/** * 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 `
${this.formatRatio(current)}
`; }, /** * 渲染分页控件 */ renderPagination(current, total, pageSize, onChange) { const totalPages = Math.ceil(total / pageSize); const start = (current - 1) * pageSize + 1; const end = Math.min(current * pageSize, total); return ` `; }, /** * 获取分页数字 */ getPaginationNumbers(current, total) { const pages = []; const maxVisible = 5; let start = Math.max(1, current - Math.floor(maxVisible / 2)); let end = Math.min(total, start + maxVisible - 1); start = Math.max(1, end - maxVisible + 1); for (let i = start; i <= end; i++) { pages.push(` `); } return pages.join(''); }, /** * 渲染统计卡片 */ renderStatCard(icon, label, value, iconClass = 'primary', change = null) { let changeHtml = ''; if (change !== null) { const changeClass = change >= 0 ? 'positive' : 'negative'; const changeIcon = change >= 0 ? 'trending-up' : 'trending-down'; changeHtml = `
${Math.abs(change)}%
`; } return `
${label}
${value}
${changeHtml}
`; }, /** * 渲染空状态 */ renderEmptyState(icon = 'inbox', title = '暂无数据', description = '') { return `
${title}
${description ? `
${description}
` : ''}
`; }, /** * 渲染信息列表项 */ renderInfoItem(label, value) { return `
${label} ${value || '-'}
`; }, /** * 渲染 Markdown 内容 */ renderMarkdown(content) { if (!content) return ''; if (typeof marked !== 'undefined') { let html = marked.parse(content); html = html.replace(/
\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 = ` ${message} `; 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;