/**
* 主应用逻辑
*/
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 = `
`;
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)}
配件种类数
${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(``);
}
// 推理过程(可折叠)
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(``);
}
}
// 库销比诊断
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(``);
}
}
// 结构分析
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(``);
}
}
// 构成诊断(销量分析)
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(``);
}
}
// 活跃度诊断
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(``);
}
}
// 趋势诊断
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(``);
}
}
// 健康度诊断
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(``);
}
}
// 问题诊断(健康度)
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(``);
}
}
// 资金释放计算
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(``);
}
}
// 紧迫度诊断(补货建议)
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(``);
}
}
// 预算分析
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(``);
}
}
// 风险识别
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(``);
}
}
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 => `
|
${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)} |
`).join('')}
`;
},
/**
* 显示任务列表页面
*/
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 = `
| 任务编号 |
商家组合 |
状态 |
配件数 |
建议金额 |
基准库销比 |
统计日期 |
执行时长 |
操作 |
${items.map(task => `
|
${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)} |
查看
|
`).join('')}
`;
// 渲染分页
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 = `
返回任务列表
${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 = `
|
配件编码 ${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')}
|
${items.length > 0 ? items.map((item, index) => `
|
|
${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)} |
|
|
`).join('') : `
|
暂无符合条件的配件建议
|
`}
`;
// 渲染分页
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 => `
| ${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 || '-'}
|
`).join('')}
`;
},
/**
* 渲染执行日志标签页
*/
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 = `
${items.map((log, index) => `
${log.status === 1 ? '' :
log.status === 2 ? '' :
''}
${index < items.length - 1 ? '
' : ''}
${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)}
${Components.renderInfoItem('LLM 提供商', task.llm_provider || '-')}
${Components.renderInfoItem('模型名称', task.llm_model || '-')}
${Components.renderInfoItem('Token 消耗', task.llm_total_tokens)}
${task.error_message ? `
` : ''}
`;
},
};
// DOM 加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
App.init();
});
// 导出到全局
window.App = App;