/** * 主应用逻辑 */ const App = { currentPage: 'dashboard', currentTaskNo: null, // 配件汇总表排序状态 partSummarySort: { sortBy: 'total_suggest_amount', sortOrder: 'desc' }, // 配件汇总表筛选状态 partSummaryFilters: { page: 1, page_size: 50, part_code: '', priority: '' }, // 侧边栏折叠状态 isSidebarCollapsed: localStorage.getItem('sidebar-collapsed') === 'true', /** * 切换侧边栏折叠状态 */ toggleSidebarCollapse() { this.isSidebarCollapsed = !this.isSidebarCollapsed; localStorage.setItem('sidebar-collapsed', this.isSidebarCollapsed); this.applySidebarState(); }, /** * 应用侧边栏状态 */ applySidebarState() { const sidebar = document.querySelector('.sidebar'); // 主内容区通过 CSS 选择器 .sidebar.collapsed + ... .main-content 自动调整 // 或者我们需要手动给 main-content 加类,但 CSS 中用了兄弟选择器, // 不过兄弟选择器在这里可能不生效,因为中间隔了 .sidebar-overlay // 让我们看看 index.html 结构: sidebar, sidebar-overlay, main-content // CSS 选择器是 .sidebar.collapsed + .sidebar-overlay + .main-content // 这样是可以的。 if (this.isSidebarCollapsed) { sidebar.classList.add('collapsed'); } else { sidebar.classList.remove('collapsed'); } // 触发 icon 刷新以确保显示正确(虽然 CSS 旋转已处理) lucide.createIcons(); }, /** * 切换侧边栏显示状态(移动端) */ toggleSidebar(show) { const sidebar = document.querySelector('.sidebar'); const overlay = document.getElementById('sidebar-overlay'); if (show) { sidebar.classList.add('visible'); overlay.classList.add('active'); } else { sidebar.classList.remove('visible'); overlay.classList.remove('active'); } }, /** * 应用配件筛选 */ applyPartFilters() { const partCode = document.getElementById('filter-part-code')?.value || ''; const priorityElement = document.getElementById('filter-priority'); const priority = priorityElement ? priorityElement.value : ''; this.partSummaryFilters.part_code = partCode; this.partSummaryFilters.priority = priority; this.partSummaryFilters.page = 1; // 重置到第一页 this.loadPartSummaries(); }, /** * 重置配件筛选 */ resetPartFilters() { this.partSummaryFilters.part_code = ''; this.partSummaryFilters.priority = ''; this.partSummaryFilters.page = 1; this.loadPartSummaries(); }, /** * 渲染分析报告标签页(四大板块:库存概览/销量分析/健康度/补货建议) */ async renderReportTab(container, taskNo) { container.innerHTML = '
加载分析报告...
'; try { const report = await API.getAnalysisReport(taskNo); if (!report) { container.innerHTML = `
${Components.renderEmptyState('file-x', '暂无分析报告', '该任务尚未生成分析报告')}
`; return; } container.innerHTML = `
`; this.renderInventoryOverview( document.getElementById('report-inventory-overview'), report.inventory_overview ); this.renderSalesAnalysis( document.getElementById('report-sales-analysis'), report.sales_analysis ); this.renderInventoryHealth( document.getElementById('report-inventory-health'), report.inventory_health ); this.renderReplenishmentSummary( document.getElementById('report-replenishment-summary'), report.replenishment_summary ); lucide.createIcons(); } catch (error) { container.innerHTML = `

加载报告失败: ${error.message}

`; lucide.createIcons(); } }, /** * 渲染库存概览板块 */ renderInventoryOverview(container, data) { if (!data) { container.innerHTML = ''; return; } const stats = data.stats || {}; const analysis = data.llm_analysis || {}; // 兼容新旧数据结构 const conclusion = analysis.conclusion || analysis; const process = analysis.analysis_process || null; const ratio = stats.overall_ratio; const ratioDisplay = (ratio === 999 || ratio === null || ratio === undefined) ? '无销量' : Components.formatNumber(ratio); // LLM 分析文本渲染 let analysisHtml = ''; if (analysis.error) { analysisHtml = `
分析生成失败: ${analysis.error}
`; } else { const sections = []; // 季节信息(如果有) if (process && process.seasonal_analysis) { const sa = process.seasonal_analysis; sections.push(`
季节性分析 ${sa.current_season || ''}

${sa.season_demand_feature || ''}

${sa.inventory_fitness || ''}

${sa.upcoming_season_preparation ? `

下季准备: ${sa.upcoming_season_preparation}

` : ''}
`); } if (conclusion.capital_assessment) { const ca = conclusion.capital_assessment; sections.push(`
资金占用评估 ${ca.risk_level === 'high' ? '高风险' : ca.risk_level === 'low' ? '低风险' : '中风险'}

${ca.total_evaluation || ''}

${ca.structure_ratio || ''}

`); } if (conclusion.ratio_diagnosis) { const rd = conclusion.ratio_diagnosis; sections.push(`
库销比诊断 — ${rd.level || ''}

${rd.analysis || ''}

${rd.benchmark || ''}

`); } if (conclusion.recommendations && conclusion.recommendations.length > 0) { const recHtml = conclusion.recommendations.map(r => { if (typeof r === 'object') { return `
  • ${r.action || ''}${r.reason ? ` - ${r.reason}` : ''}${r.expected_effect ? `
    预期效果: ${r.expected_effect}` : ''}
  • `; } return `
  • ${r}
  • `; }).join(''); sections.push(`
    库存结构建议
    `); } // 推理过程(可折叠) let processHtml = ''; if (process) { processHtml = this._renderAnalysisProcess(process, 'inventory-overview'); } analysisHtml = sections.length > 0 ? `
    ${sections.join('')}${processHtml}
    ` : ''; } container.innerHTML = `
    库存总体概览
    有效库存总数量
    ${Components.formatNumber(stats.total_valid_storage_cnt)}
    资金占用(总金额)
    ${Components.formatAmount(stats.total_valid_storage_amount)}
    整体库销比
    ${ratioDisplay}
    配件种类数
    ${stats.part_count || 0}
    构成项 数量 金额
    在库未锁 ${Components.formatNumber(stats.total_in_stock_unlocked_cnt)} ${Components.formatAmount(stats.total_in_stock_unlocked_amount)}
    在途 ${Components.formatNumber(stats.total_on_the_way_cnt)} ${Components.formatAmount(stats.total_on_the_way_amount)}
    计划数 ${Components.formatNumber(stats.total_has_plan_cnt)} ${Components.formatAmount(stats.total_has_plan_amount)}
    ${analysisHtml} `; // 绑定折叠事件 this._bindProcessToggle(container); }, /** * 渲染销量分析板块 */ renderSalesAnalysis(container, data) { if (!data) { container.innerHTML = ''; return; } const stats = data.stats || {}; const analysis = data.llm_analysis || {}; // 兼容新旧数据结构 const conclusion = analysis.conclusion || analysis; const process = analysis.analysis_process || null; // LLM 分析文本 let analysisHtml = ''; if (analysis.error) { analysisHtml = `
    分析生成失败: ${analysis.error}
    `; } else { const sections = []; // 季节信息(如果有) if (process && process.seasonal_analysis) { const sa = process.seasonal_analysis; sections.push(`
    季节性分析 ${sa.current_season || ''}

    ${sa.expected_performance || ''}

    ${sa.actual_vs_expected || ''}

    ${sa.seasonal_items_status ? `

    ${sa.seasonal_items_status}

    ` : ''}
    `); } if (conclusion.composition_analysis) { const ca = conclusion.composition_analysis; sections.push(`
    销量构成解读

    ${ca.main_driver || ''}

    ${ca.pending_orders_impact || ''}

    ${ca.booking_trend || ''}

    `); } if (conclusion.activity_assessment) { const aa = conclusion.activity_assessment; sections.push(`
    销售活跃度

    ${aa.active_ratio || ''}

    ${aa.optimization_suggestion || ''}

    `); } if (conclusion.demand_trend) { const dt = conclusion.demand_trend; const dirIcon = dt.direction === '上升' ? 'trending-up' : dt.direction === '下降' ? 'trending-down' : 'minus'; sections.push(`
    需求趋势 — ${dt.direction || ''}

    ${dt.evidence || ''}

    ${dt.seasonal_factor ? `

    季节因素: ${dt.seasonal_factor}

    ` : ''}

    ${dt.forecast || ''}

    `); } // 推理过程(可折叠) let processHtml = ''; if (process) { processHtml = this._renderAnalysisProcess(process, 'sales-analysis'); } analysisHtml = sections.length > 0 ? `
    ${sections.join('')}${processHtml}
    ` : ''; } const totalParts = (stats.has_sales_part_count || 0) + (stats.no_sales_part_count || 0); container.innerHTML = `
    销量分析
    月均销量总数量
    ${Components.formatNumber(stats.total_avg_sales_cnt)}
    月均销量总金额
    ${Components.formatAmount(stats.total_avg_sales_amount)}
    有销量配件
    ${stats.has_sales_part_count || 0} / ${totalParts}
    无销量配件
    ${stats.no_sales_part_count || 0} / ${totalParts}
    构成项 总量
    90天出库数 ${Components.formatNumber(stats.total_out_stock_cnt)}
    未关单已锁 ${Components.formatNumber(stats.total_storage_locked_cnt)}
    未关单出库 ${Components.formatNumber(stats.total_out_stock_ongoing_cnt)}
    订件 ${Components.formatNumber(stats.total_buy_cnt)}
    ${analysisHtml} `; // 绑定折叠事件 this._bindProcessToggle(container); }, /** * 渲染库存健康度板块(含 Chart.js 环形图) */ renderInventoryHealth(container, data) { if (!data) { container.innerHTML = ''; return; } const stats = data.stats || {}; const chartData = data.chart_data || {}; const analysis = data.llm_analysis || {}; // 兼容新旧数据结构 const conclusion = analysis.conclusion || analysis; const process = analysis.analysis_process || null; const shortage = stats.shortage || {}; const stagnant = stats.stagnant || {}; const low_freq = stats.low_freq || {}; const normal = stats.normal || {}; // LLM 分析文本 let analysisHtml = ''; if (analysis.error) { analysisHtml = `
    分析生成失败: ${analysis.error}
    `; } else { const sections = []; // 季节信息(如果有) if (process && process.seasonal_analysis) { const sa = process.seasonal_analysis; sections.push(`
    季节性分析 ${sa.current_season || ''}
    ${sa.seasonal_stagnant_items ? `

    ${sa.seasonal_stagnant_items}

    ` : ''} ${sa.seasonal_shortage_risk ? `

    ${sa.seasonal_shortage_risk}

    ` : ''} ${sa.upcoming_season_alert ? `

    下季关注: ${sa.upcoming_season_alert}

    ` : ''}
    `); } if (conclusion.health_score) { const hs = conclusion.health_score; sections.push(`
    健康度评分 — ${hs.score || ''}

    ${hs.normal_ratio_evaluation || ''}

    `); } if (conclusion.problem_diagnosis) { const pd = conclusion.problem_diagnosis; sections.push(`
    问题诊断
    ${pd.stagnant_analysis ? `

    呆滞件: ${pd.stagnant_analysis}

    ` : ''} ${pd.shortage_analysis ? `

    缺货件: ${pd.shortage_analysis}

    ` : ''} ${pd.low_freq_analysis ? `

    低频件: ${pd.low_freq_analysis}

    ` : ''}
    `); } if (conclusion.capital_release) { const cr = conclusion.capital_release; sections.push(`
    资金释放机会
    ${cr.stagnant_releasable ? `

    呆滞件可释放: ${cr.stagnant_releasable}

    ` : ''} ${cr.low_freq_releasable ? `

    低频件可释放: ${cr.low_freq_releasable}

    ` : ''} ${cr.action_plan ? `

    ${cr.action_plan}

    ` : ''}
    `); } if (conclusion.priority_actions && conclusion.priority_actions.length > 0) { const actHtml = conclusion.priority_actions.map(a => { if (typeof a === 'object') { return `
  • ${a.action || ''}${a.reason ? ` - ${a.reason}` : ''}${a.expected_effect ? `
    预期效果: ${a.expected_effect}` : ''}
  • `; } return `
  • ${a}
  • `; }).join(''); sections.push(`
    改善优先级
      ${actHtml}
    `); } // 推理过程(可折叠) let processHtml = ''; if (process) { processHtml = this._renderAnalysisProcess(process, 'inventory-health'); } analysisHtml = sections.length > 0 ? `
    ${sections.join('')}${processHtml}
    ` : ''; } container.innerHTML = `
    库存构成健康度
    缺货件
    ${shortage.count || 0}
    ${Components.formatNumber(shortage.count_pct)}% · ${Components.formatAmount(shortage.amount)}
    呆滞件
    ${stagnant.count || 0}
    ${Components.formatNumber(stagnant.count_pct)}% · ${Components.formatAmount(stagnant.amount)}
    低频件
    ${low_freq.count || 0}
    ${Components.formatNumber(low_freq.count_pct)}% · ${Components.formatAmount(low_freq.amount)}
    正常件
    ${normal.count || 0}
    ${Components.formatNumber(normal.count_pct)}% · ${Components.formatAmount(normal.amount)}
    数量占比
    金额占比
    ${analysisHtml} `; // 渲染 Chart.js 环形图 this._renderHealthCharts(chartData); // 绑定折叠事件 this._bindProcessToggle(container); }, /** * 渲染健康度环形图 */ _renderHealthCharts(chartData) { if (!chartData || !chartData.labels) return; if (typeof Chart === 'undefined') return; const colors = ['#ef4444', '#f59e0b', '#3b82f6', '#10b981']; const borderColors = ['#dc2626', '#d97706', '#2563eb', '#059669']; const chartOptions = { responsive: true, maintainAspectRatio: true, plugins: { legend: { position: 'bottom', labels: { color: '#94a3b8', padding: 16, usePointStyle: true, pointStyleWidth: 10, font: { size: 12 } } }, tooltip: { backgroundColor: '#1e293b', titleColor: '#f8fafc', bodyColor: '#94a3b8', borderColor: 'rgba(148,163,184,0.2)', borderWidth: 1 } }, cutout: '60%' }; // 数量占比图 const countCtx = document.getElementById('health-count-chart'); if (countCtx) { new Chart(countCtx, { type: 'doughnut', data: { labels: chartData.labels, datasets: [{ data: chartData.count_values, backgroundColor: colors, borderColor: borderColors, borderWidth: 2 }] }, options: chartOptions }); } // 金额占比图 const amountCtx = document.getElementById('health-amount-chart'); if (amountCtx) { new Chart(amountCtx, { type: 'doughnut', data: { labels: chartData.labels, datasets: [{ data: chartData.amount_values, backgroundColor: colors, borderColor: borderColors, borderWidth: 2 }] }, options: { ...chartOptions, plugins: { ...chartOptions.plugins, tooltip: { ...chartOptions.plugins.tooltip, callbacks: { label: function(context) { const value = context.parsed; return ` ${context.label}: ¥${Number(value).toLocaleString('zh-CN', {minimumFractionDigits: 2})}`; } } } } } }); } }, /** * 渲染补货建议板块 */ renderReplenishmentSummary(container, data) { if (!data) { container.innerHTML = ''; return; } const stats = data.stats || {}; const analysis = data.llm_analysis || {}; // 兼容新旧数据结构 const conclusion = analysis.conclusion || analysis; const process = analysis.analysis_process || null; const urgent = stats.urgent || {}; const suggested = stats.suggested || {}; const optional = stats.optional || {}; // LLM 分析文本 let analysisHtml = ''; if (analysis.error) { analysisHtml = `
    分析生成失败: ${analysis.error}
    `; } else { const sections = []; // 季节信息(如果有) if (process && process.seasonal_analysis) { const sa = process.seasonal_analysis; sections.push(`
    季节性分析 ${sa.current_season || ''}
    ${sa.seasonal_priority_items ? `

    ${sa.seasonal_priority_items}

    ` : ''} ${sa.timeline_adjustment ? `

    ${sa.timeline_adjustment}

    ` : ''} ${sa.next_season_preparation ? `

    下季准备: ${sa.next_season_preparation}

    ` : ''}
    `); } if (conclusion.urgency_assessment) { const ua = conclusion.urgency_assessment; const riskTag = ua.risk_level === 'high' ? '高风险' : ua.risk_level === 'low' ? '低风险' : '中风险'; sections.push(`
    紧迫度评估 ${riskTag}

    ${ua.urgent_ratio_evaluation || ''}

    ${ua.immediate_action_needed ? '

    需要立即采取行动

    ' : ''}
    `); } if (conclusion.budget_allocation) { const ba = conclusion.budget_allocation; sections.push(`
    资金分配建议

    ${ba.recommended_order || ''}

    ${ba.urgent_budget ? `

    急需补货预算: ${ba.urgent_budget}

    ` : ''} ${ba.suggested_budget ? `

    建议补货预算: ${ba.suggested_budget}

    ` : ''} ${ba.optional_budget ? `

    可选补货预算: ${ba.optional_budget}

    ` : ''}
    `); } if (conclusion.execution_plan) { const ep = conclusion.execution_plan; sections.push(`
    执行节奏建议
    ${ep.urgent_timeline ? `

    急需: ${ep.urgent_timeline}

    ` : ''} ${ep.suggested_timeline ? `

    建议: ${ep.suggested_timeline}

    ` : ''} ${ep.optional_timeline ? `

    可选: ${ep.optional_timeline}

    ` : ''}
    `); } if (conclusion.risk_warnings && conclusion.risk_warnings.length > 0) { const warnHtml = conclusion.risk_warnings.map(w => { if (typeof w === 'object') { return `
  • ${w.risk_type || ''}: ${w.description || ''}${w.mitigation ? `
    应对: ${w.mitigation}` : ''}
  • `; } return `
  • ${w}
  • `; }).join(''); sections.push(`
    风险提示
    `); } // 推理过程(可折叠) let processHtml = ''; if (process) { processHtml = this._renderAnalysisProcess(process, 'replenishment-summary'); } analysisHtml = sections.length > 0 ? `
    ${sections.join('')}${processHtml}
    ` : ''; } container.innerHTML = `
    补货建议生成情况
    优先级 配件种类数 建议补货金额
    急需补货 ${urgent.count || 0} ${Components.formatAmount(urgent.amount)}
    建议补货 ${suggested.count || 0} ${Components.formatAmount(suggested.amount)}
    可选补货 ${optional.count || 0} ${Components.formatAmount(optional.amount)}
    合计 ${stats.total_count || 0} ${Components.formatAmount(stats.total_amount)}
    ${analysisHtml} `; // 绑定折叠事件 this._bindProcessToggle(container); }, /** * 渲染推理过程(可折叠) */ _renderAnalysisProcess(process, moduleId) { if (!process) return ''; const sections = []; // 计算指标 if (process.calculated_metrics) { const items = Object.entries(process.calculated_metrics) .filter(([k, v]) => v && v !== '') .map(([k, v]) => `
    ${this._formatProcessKey(k)}${v}
    `) .join(''); if (items) { sections.push(`
    计算指标
    ${items}
    `); } } // 库销比诊断 if (process.ratio_diagnosis) { const rd = process.ratio_diagnosis; const items = []; if (rd.current_value) items.push(`
    当前值${rd.current_value}
    `); if (rd.level) items.push(`
    判断等级${rd.level}
    `); if (rd.reasoning) items.push(`
    判断依据${rd.reasoning}
    `); if (rd.benchmark_comparison) items.push(`
    行业对比${rd.benchmark_comparison}
    `); if (items.length > 0) { sections.push(`
    库销比诊断
    ${items.join('')}
    `); } } // 结构分析 if (process.structure_analysis) { const sa = process.structure_analysis; const items = []; if (sa.in_stock_evaluation) items.push(`
    在库未锁${sa.in_stock_evaluation}
    `); if (sa.on_way_evaluation) items.push(`
    在途${sa.on_way_evaluation}
    `); if (sa.plan_evaluation) items.push(`
    计划数${sa.plan_evaluation}
    `); if (sa.abnormal_items && sa.abnormal_items.length > 0) { items.push(`
    异常项${sa.abnormal_items.join('; ')}
    `); } if (items.length > 0) { sections.push(`
    结构分析
    ${items.join('')}
    `); } } // 构成诊断(销量分析) if (process.composition_diagnosis) { const cd = process.composition_diagnosis; const items = []; if (cd.out_stock_evaluation) items.push(`
    90天出库${cd.out_stock_evaluation}
    `); if (cd.locked_evaluation) items.push(`
    未关单已锁${cd.locked_evaluation}
    `); if (cd.ongoing_evaluation) items.push(`
    未关单出库${cd.ongoing_evaluation}
    `); if (cd.buy_evaluation) items.push(`
    订件${cd.buy_evaluation}
    `); if (items.length > 0) { sections.push(`
    构成诊断
    ${items.join('')}
    `); } } // 活跃度诊断 if (process.activity_diagnosis) { const ad = process.activity_diagnosis; const items = []; if (ad.current_rate) items.push(`
    当前活跃率${ad.current_rate}
    `); if (ad.level) items.push(`
    判断等级${ad.level}
    `); if (ad.reasoning) items.push(`
    判断依据${ad.reasoning}
    `); if (items.length > 0) { sections.push(`
    活跃度诊断
    ${items.join('')}
    `); } } // 趋势诊断 if (process.trend_diagnosis) { const td = process.trend_diagnosis; const items = []; if (td.signals && td.signals.length > 0) items.push(`
    趋势信号${td.signals.join('; ')}
    `); if (td.reasoning) items.push(`
    判断依据${td.reasoning}
    `); if (items.length > 0) { sections.push(`
    趋势诊断
    ${items.join('')}
    `); } } // 健康度诊断 if (process.health_score_diagnosis) { const hsd = process.health_score_diagnosis; const items = []; if (hsd.normal_ratio) items.push(`
    正常件占比${hsd.normal_ratio}
    `); if (hsd.score) items.push(`
    健康度评分${hsd.score}
    `); if (hsd.reasoning) items.push(`
    判断依据${hsd.reasoning}
    `); if (items.length > 0) { sections.push(`
    健康度诊断
    ${items.join('')}
    `); } } // 问题诊断(健康度) if (process.problem_diagnosis) { const pd = process.problem_diagnosis; const items = []; ['shortage', 'stagnant', 'low_freq'].forEach(key => { const item = pd[key]; if (item) { const label = key === 'shortage' ? '缺货件' : key === 'stagnant' ? '呆滞件' : '低频件'; if (item.threshold_comparison) items.push(`
    ${label}${item.threshold_comparison}
    `); } }); if (items.length > 0) { sections.push(`
    问题诊断
    ${items.join('')}
    `); } } // 资金释放计算 if (process.capital_release_calculation) { const crc = process.capital_release_calculation; const items = []; if (crc.stagnant_calculation) items.push(`
    呆滞件${crc.stagnant_calculation}
    `); if (crc.low_freq_calculation) items.push(`
    低频件${crc.low_freq_calculation}
    `); if (crc.total_releasable) items.push(`
    总可释放${crc.total_releasable}
    `); if (items.length > 0) { sections.push(`
    资金释放计算
    ${items.join('')}
    `); } } // 紧迫度诊断(补货建议) if (process.urgency_diagnosis) { const ud = process.urgency_diagnosis; const items = []; if (ud.urgent_ratio) items.push(`
    急需占比${ud.urgent_ratio}
    `); if (ud.level) items.push(`
    紧迫等级${ud.level}
    `); if (ud.reasoning) items.push(`
    判断依据${ud.reasoning}
    `); if (items.length > 0) { sections.push(`
    紧迫度诊断
    ${items.join('')}
    `); } } // 预算分析 if (process.budget_analysis) { const ba = process.budget_analysis; const items = []; if (ba.current_distribution) items.push(`
    当前分布${ba.current_distribution}
    `); if (ba.comparison_with_standard) items.push(`
    标准对比${ba.comparison_with_standard}
    `); if (ba.adjustment_needed) items.push(`
    调整建议${ba.adjustment_needed}
    `); if (items.length > 0) { sections.push(`
    预算分析
    ${items.join('')}
    `); } } // 风险识别 if (process.risk_identification) { const ri = process.risk_identification; const items = []; if (ri.capital_pressure_check) items.push(`
    资金压力${ri.capital_pressure_check}
    `); if (ri.over_replenishment_check) items.push(`
    过度补货${ri.over_replenishment_check}
    `); if (ri.identified_risks && ri.identified_risks.length > 0) { items.push(`
    识别风险${ri.identified_risks.join('; ')}
    `); } if (items.length > 0) { sections.push(`
    风险识别
    ${items.join('')}
    `); } } if (sections.length === 0) return ''; return `
    查看分析推理过程
    ${sections.join('')}
    `; }, /** * 格式化推理过程的key名称 */ _formatProcessKey(key) { const keyMap = { 'in_stock_ratio': '在库未锁占比', 'on_way_ratio': '在途占比', 'plan_ratio': '计划数占比', 'avg_cost': '平均成本', 'out_stock_ratio': '90天出库占比', 'locked_ratio': '未关单已锁占比', 'ongoing_ratio': '未关单出库占比', 'buy_ratio': '订件占比', 'sku_active_rate': 'SKU活跃率', 'avg_sales_price': '平均销售金额', 'urgent_count_ratio': '急需数量占比', 'urgent_amount_ratio': '急需金额占比', 'suggested_count_ratio': '建议数量占比', 'suggested_amount_ratio': '建议金额占比', 'optional_count_ratio': '可选数量占比', 'optional_amount_ratio': '可选金额占比', }; return keyMap[key] || key; }, /** * 绑定推理过程折叠事件 */ _bindProcessToggle(container) { const toggles = container.querySelectorAll('.analysis-process-toggle'); toggles.forEach(toggle => { toggle.addEventListener('click', () => { const targetId = toggle.dataset.target; const content = document.getElementById(targetId); if (content) { toggle.classList.toggle('expanded'); content.classList.toggle('expanded'); lucide.createIcons(); } }); }); }, /** * 初始化应用 */ init() { this.bindEvents(); this.handleRoute(); this.applySidebarState(); // 初始化侧边栏状态 window.addEventListener('hashchange', () => this.handleRoute()); lucide.createIcons(); }, /** * 绑定全局事件 */ bindEvents() { // 刷新按钮 document.getElementById('refresh-btn').addEventListener('click', () => { this.handleRoute(); }); // 模态框关闭 document.getElementById('modal-close').addEventListener('click', () => { Components.closeModal(); }); document.getElementById('modal-overlay').addEventListener('click', (e) => { if (e.target.id === 'modal-overlay') { Components.closeModal(); } }); // 侧边栏切换 const menuToggle = document.getElementById('menu-toggle'); const sidebarOverlay = document.getElementById('sidebar-overlay'); if (menuToggle) { menuToggle.addEventListener('click', () => { this.toggleSidebar(true); }); } if (sidebarOverlay) { sidebarOverlay.addEventListener('click', () => { this.toggleSidebar(false); }); } // 桌面端侧边栏折叠按钮 const collapseBtn = document.getElementById('sidebar-collapse-btn'); if (collapseBtn) { collapseBtn.addEventListener('click', () => { this.toggleSidebarCollapse(); }); } // 导航点击自动关闭侧边栏(移动端) document.querySelectorAll('.nav-item').forEach(item => { item.addEventListener('click', () => { if (window.innerWidth <= 1024) { this.toggleSidebar(false); } }); }); }, /** * 路由处理 */ handleRoute() { const hash = window.location.hash || '#/'; const [, path, param] = hash.match(/#\/([^/]*)(?:\/(.*))?/) || [, '', '']; // 更新导航状态 document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); if (item.dataset.page === (path || 'dashboard')) { item.classList.add('active'); } }); // 路由分发 switch (path) { case 'tasks': if (param) { this.showTaskDetail(param); } else { this.showTaskList(); } break; case '': default: this.showDashboard(); break; } }, /** * 更新面包屑 */ updateBreadcrumb(items) { const breadcrumb = document.getElementById('breadcrumb'); breadcrumb.innerHTML = items.map((item, index) => { if (item.href) { return `${item.text}`; } return `${item.text}`; }).join(''); }, /** * 显示概览页面 */ async showDashboard() { this.currentPage = 'dashboard'; this.updateBreadcrumb([{ text: '概览' }]); const container = document.getElementById('page-container'); container.innerHTML = '
    '; try { // 获取统计数据 const [stats, tasksData] = await Promise.all([ API.getStatsSummary().catch(() => ({})), API.getTasks({ page: 1, page_size: 5 }).catch(() => ({ items: [] })), ]); // 渲染统计卡片 const statsGrid = document.getElementById('stats-grid'); statsGrid.innerHTML = ` ${Components.renderStatCard('list-checks', '总任务数', stats.total_tasks || 0, 'primary')} ${Components.renderStatCard('check-circle', '成功任务', stats.success_tasks || 0, 'success')} ${Components.renderStatCard('x-circle', '失败任务', stats.failed_tasks || 0, 'danger')} ${Components.renderStatCard('package', '建议配件', stats.total_parts || 0, 'info')} ${Components.renderStatCard('dollar-sign', '建议金额', Components.formatAmount(stats.total_suggest_amount), 'warning')} `; // 渲染最近任务 this.renderRecentTasks(tasksData.items || []); lucide.createIcons(); } catch (error) { Components.showToast('加载数据失败: ' + error.message, 'error'); } }, /** * 渲染最近任务 */ renderRecentTasks(tasks) { const container = document.getElementById('recent-tasks'); if (!tasks.length) { container.innerHTML = `

    最近任务

    ${Components.renderEmptyState('inbox', '暂无任务', '还没有执行过任何补货建议任务')}
    `; return; } container.innerHTML = `

    最近任务

    查看全部
    ${tasks.map(task => ` `).join('')}
    任务编号 商家组合 状态 配件数 建议金额 执行时间
    ${task.task_no} ${task.dealer_grouping_name || '-'} ${Components.getStatusBadge(task.status, task.status_text)} ${task.part_count} ${Components.formatAmount(task.actual_amount)} ${Components.formatDuration(task.duration_seconds)}
    `; }, /** * 显示任务列表页面 */ async showTaskList(page = 1) { this.currentPage = 'tasks'; this.updateBreadcrumb([{ text: '任务列表' }]); const container = document.getElementById('page-container'); container.innerHTML = '
    '; try { Components.showLoading(); const data = await API.getTasks({ page, page_size: 20 }); Components.hideLoading(); this.renderTaskList(data); lucide.createIcons(); } catch (error) { Components.hideLoading(); Components.showToast('加载任务列表失败: ' + error.message, 'error'); } }, /** * 渲染任务列表 */ renderTaskList(data) { const container = document.getElementById('task-list-container'); const { items, total, page, page_size } = data; if (!items.length) { container.innerHTML = `
    ${Components.renderEmptyState('inbox', '暂无任务', '还没有执行过任何补货建议任务')}
    `; return; } container.innerHTML = `

    任务列表 (${total})

    ${items.map(task => ` `).join('')}
    任务编号 商家组合 状态 配件数 建议金额 基准库销比 统计日期 执行时长 操作
    ${task.task_no} ${task.dealer_grouping_name || '-'} ${Components.getStatusBadge(task.status, task.status_text)} ${task.part_count} ${Components.formatAmount(task.actual_amount)} ${Components.formatRatio(task.base_ratio)} ${task.statistics_date || '-'} ${Components.formatDuration(task.duration_seconds)} 查看
    `; // 渲染分页 const paginationContainer = document.getElementById('pagination-container'); paginationContainer.innerHTML = Components.renderPagination(page, total, page_size); // 绑定分页事件 paginationContainer.querySelectorAll('.pagination-btn[data-page]').forEach(btn => { btn.addEventListener('click', () => { const targetPage = parseInt(btn.dataset.page); if (targetPage && targetPage !== page) { this.showTaskList(targetPage); } }); }); lucide.createIcons(); }, /** * 显示任务详情页面 */ async showTaskDetail(taskNo) { this.currentPage = 'task-detail'; this.currentTaskNo = taskNo; this.updateBreadcrumb([ { text: '任务列表', href: '#/tasks' }, { text: taskNo }, ]); const container = document.getElementById('page-container'); container.innerHTML = '
    '; try { Components.showLoading(); const [task, partSummaries, logs] = await Promise.all([ API.getTask(taskNo), API.getPartSummaries(taskNo, { page: 1, page_size: 100 }).catch(() => ({ items: [], total: 0 })), API.getTaskLogs(taskNo).catch(() => ({ items: [] })), ]); Components.hideLoading(); this.renderTaskDetail(task, partSummaries, logs); lucide.createIcons(); } catch (error) { Components.hideLoading(); Components.showToast('加载任务详情失败: ' + error.message, 'error'); } }, /** * 渲染任务详情 */ renderTaskDetail(task, partSummaries, logs) { this._currentLogs = logs; this._partSummaries = partSummaries; const container = document.getElementById('task-detail-container'); container.innerHTML = ` 返回任务列表

    ${task.task_no} ${Components.getStatusBadge(task.status, task.status_text)}

    ${task.dealer_grouping_name || '未知商家组合'} ${task.statistics_date || '-'} ${Components.formatDuration(task.duration_seconds)}
    ${Components.renderStatCard('package', '建议配件数', task.part_count, 'primary')} ${Components.renderStatCard('dollar-sign', '建议金额', Components.formatAmount(task.actual_amount), 'success')} ${Components.renderStatCard('percent', '基准库销比', Components.formatRatio(task.base_ratio), 'info')} ${Components.renderStatCard('cpu', 'LLM Tokens', task.llm_total_tokens || 0, 'warning')}
    `; // 绑定标签页事件 const tabs = container.querySelectorAll('.tab'); tabs.forEach(tab => { tab.addEventListener('click', () => { tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); this.renderTabContent(tab.dataset.tab, task, partSummaries); }); }); // 默认显示配件汇总 this.renderTabContent('details', task, partSummaries); }, /** * 渲染标签页内容 */ renderTabContent(tabName, task, details) { const container = document.getElementById('tab-content'); switch (tabName) { case 'details': this.renderDetailsTab(container, details); break; case 'logs': this.renderLogsTab(container, this._currentLogs); break; case 'report': this.renderReportTab(container, task.task_no); break; case 'info': this.renderInfoTab(container, task); break; } lucide.createIcons(); }, /** * 加载配件汇总数据(支持排序和筛选) */ async loadPartSummaries() { if (!this.currentTaskNo) return; try { const params = { page: this.partSummaryFilters.page, page_size: this.partSummaryFilters.page_size, sort_by: this.partSummarySort.sortBy, sort_order: this.partSummarySort.sortOrder, part_code: this.partSummaryFilters.part_code, priority: this.partSummaryFilters.priority }; // 移除空值参数 Object.keys(params).forEach(key => { if (params[key] === '' || params[key] === null || params[key] === undefined) { delete params[key]; } }); const data = await API.getPartSummaries(this.currentTaskNo, params); this._partSummaries = data; const container = document.getElementById('tab-content'); if (container) { this.renderDetailsTab(container, data); lucide.createIcons(); } } catch (error) { Components.showToast('加载配件数据失败: ' + error.message, 'error'); } }, /** * 切换配件汇总排序 */ togglePartSummarySort(field) { if (this.partSummarySort.sortBy === field) { this.partSummarySort.sortOrder = this.partSummarySort.sortOrder === 'desc' ? 'asc' : 'desc'; } else { this.partSummarySort.sortBy = field; this.partSummarySort.sortOrder = 'desc'; } this.loadPartSummaries(); }, /** * 获取排序图标 */ getSortIcon(field) { if (this.partSummarySort.sortBy !== field) { return ''; } if (this.partSummarySort.sortOrder === 'desc') { return ''; } return ''; }, /** * 渲染配件明细标签页 */ renderDetailsTab(container, partSummaries) { const items = partSummaries.items || []; const { total, page, page_size } = partSummaries; container.innerHTML = `

    配件补货建议 (商家组合维度) - ${total}个配件

    点击表头可排序
    ${items.length > 0 ? items.map((item, index) => ` `).join('') : ` `}
    配件编码 ${this.getSortIcon('part_code')} 配件名称 成本价 ${this.getSortIcon('cost_price')} 总库存 ${this.getSortIcon('total_storage_cnt')} 总销量 ${this.getSortIcon('total_avg_sales_cnt')} 商家组合库销比 ${this.getSortIcon('group_current_ratio')} 计划后库销比 ${this.getSortIcon('group_post_plan_ratio')} 门店数 ${this.getSortIcon('shop_count')} 需补货门店 ${this.getSortIcon('need_replenishment_shop_count')} 总建议数量 ${this.getSortIcon('total_suggest_cnt')} 总建议金额 ${this.getSortIcon('total_suggest_amount')}
    ${item.part_code} ${item.part_name || '-'} ${Components.formatAmount(item.cost_price)} ${Components.formatNumber(item.total_storage_cnt)} ${Components.formatNumber(item.total_avg_sales_cnt)} ${Components.getRatioIndicator(item.group_current_ratio, 1.1)} ${Components.formatRatio(item.group_post_plan_ratio)} ${item.shop_count} ${item.need_replenishment_shop_count} ${item.total_suggest_cnt} ${Components.formatAmount(item.total_suggest_amount)}
    暂无符合条件的配件建议
    `; // 渲染分页 const paginationContainer = document.getElementById('part-summary-pagination'); if (paginationContainer) { paginationContainer.innerHTML = Components.renderPagination(page, total, page_size); // 绑定分页事件 paginationContainer.querySelectorAll('.pagination-btn[data-page]').forEach(btn => { btn.addEventListener('click', () => { const targetPage = parseInt(btn.dataset.page); if (targetPage && targetPage !== page) { this.partSummaryFilters.page = targetPage; this.loadPartSummaries(); } }); }); } // 绑定搜索框回车事件 const partCodeInput = document.getElementById('filter-part-code'); if (partCodeInput) { partCodeInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { App.applyPartFilters(); } }); } }, /** * 切换配件门店展开/收起 */ async togglePartShops(partCode, index) { const row = document.getElementById(`shops-${index}`); const container = document.getElementById(`shops-container-${index}`); const btn = document.querySelector(`tr[data-index="${index}"] .expand-icon`); if (row.style.display === 'none') { row.style.display = 'table-row'; btn.classList.add('expanded'); try { const data = await API.getPartShopDetails(this.currentTaskNo, partCode); const partSummary = this._partSummaries.items.find(p => p.part_code === partCode); this.renderPartShops(container, data.items, partSummary); lucide.createIcons(); } catch (error) { container.innerHTML = `
    加载失败: ${error.message}
    `; } } else { row.style.display = 'none'; btn.classList.remove('expanded'); } }, /** * 渲染配件门店明细 */ renderPartShops(container, shops, partSummary) { if (!shops || shops.length === 0) { container.innerHTML = '
    无门店建议数据
    '; return; } container.innerHTML = ` ${partSummary && partSummary.part_decision_reason ? `
    配件补货理由: ${partSummary.part_decision_reason}
    ` : ''} ${shops.map(shop => ` `).join('')}
    库房 有效库存 月均销量 当前库销比 计划后库销比 建议数量 建议金额 建议理由
    ${shop.shop_name || '-'} ${Components.formatNumber(shop.valid_storage_cnt)} ${Components.formatNumber(shop.avg_sales_cnt)} ${Components.getRatioIndicator(shop.current_ratio, shop.base_ratio)} ${Components.formatRatio(shop.post_plan_ratio)} ${shop.suggest_cnt} ${Components.formatAmount(shop.suggest_amount)} ${shop.suggestion_reason || '-'}
    `; }, /** * 渲染执行日志标签页 */ renderLogsTab(container, logs) { if (!logs || !logs.items || logs.items.length === 0) { container.innerHTML = `
    ${Components.renderEmptyState('activity', '暂无执行日志', '该任务没有执行日志记录')}
    `; return; } const items = logs.items; const totalTokens = items.reduce((sum, item) => sum + (item.llm_tokens || 0), 0); const totalTime = items.reduce((sum, item) => sum + (item.execution_time_ms || 0), 0); container.innerHTML = `

    执行日志时间线

    总耗时: ${Components.formatDuration(totalTime / 1000)} | 总Tokens: ${totalTokens}
    ${items.map((log, index) => `
    ${log.status === 1 ? '' : log.status === 2 ? '' : ''}
    ${index < items.length - 1 ? '
    ' : ''}
    ${Components.getStepNameDisplay(log.step_name)} ${Components.getLogStatusBadge(log.status)}
    ${log.execution_time_ms ? Components.formatDuration(log.execution_time_ms / 1000) : '-'} ${log.llm_tokens > 0 ? ` ${log.llm_tokens} tokens ` : ''} ${log.retry_count > 0 ? ` 重试 ${log.retry_count} 次 ` : ''}
    ${log.error_message ? `
    ${log.error_message}
    ` : ''}
    `).join('')}
    `; }, /** * 渲染任务信息标签页 */ renderInfoTab(container, task) { container.innerHTML = `

    基本信息

    ${Components.renderInfoItem('任务编号', task.task_no)} ${Components.renderInfoItem('集团ID', task.group_id)} ${Components.renderInfoItem('商家组合ID', task.dealer_grouping_id)} ${Components.renderInfoItem('商家组合名称', task.dealer_grouping_name)} ${Components.renderInfoItem('品牌组合ID', task.brand_grouping_id)} ${Components.renderInfoItem('统计日期', task.statistics_date)}

    执行信息

    ${Components.renderInfoItem('状态', Components.getStatusBadge(task.status, task.status_text))} ${Components.renderInfoItem('开始时间', task.start_time)} ${Components.renderInfoItem('结束时间', task.end_time)} ${Components.renderInfoItem('执行时长', Components.formatDuration(task.duration_seconds))} ${Components.renderInfoItem('创建时间', task.create_time)}

    LLM 信息

    ${Components.renderInfoItem('LLM 提供商', task.llm_provider || '-')} ${Components.renderInfoItem('模型名称', task.llm_model || '-')} ${Components.renderInfoItem('Token 消耗', task.llm_total_tokens)}
    ${task.error_message ? `

    错误信息

    ${task.error_message}
    ` : ''}
    `; }, }; // DOM 加载完成后初始化 document.addEventListener('DOMContentLoaded', () => { App.init(); }); // 导出到全局 window.App = App;