強化 MOMO PChome 比價可信鏈路
All checks were successful
CD Pipeline / deploy (push) Successful in 1m45s

This commit is contained in:
OoO
2026-05-19 15:26:10 +08:00
parent ec378258e7
commit dafde7e1a7
32 changed files with 1794 additions and 117 deletions

View File

@@ -76,6 +76,8 @@
// -- Safe data extraction ---------------------------------------------
const cd = dailySalesData.chartData || {};
const competitor = dailySalesData.competitor || {};
const competitorTrend = competitor.trend || {};
const safe = {
labels: cd.labels || [],
revenue: cd.revenue || [],
@@ -206,6 +208,12 @@
mode: 'currency',
horizontal: true
});
if (competitorTrend.labels) {
renderHtmlBars('competitorGapChart', competitorTrend.labels, competitorTrend.avg_gap_pct, {
mode: 'pct',
limit: 14
});
}
}
function hasSeriesData(labels, ...seriesList) {
@@ -429,6 +437,73 @@
}));
}
// -- Chart 5: Competitor gap pressure --------------------------------
function renderCompetitorGap() {
const el = document.getElementById('competitorGapChart');
if (!el) return;
const labels = competitorTrend.labels || [];
const avgGap = competitorTrend.avg_gap_pct || [];
const riskCount = competitorTrend.risk_count || [];
if (!hasSeriesData(labels, avgGap, riskCount)) {
renderChartEmpty('competitorGapChart', '目前沒有高信心 PChome 歷史價差序列。');
return;
}
rememberChart(new Chart(el, {
type: 'bar',
data: {
labels,
datasets: [
{
label: 'MOMO 較 PChome 貴 SKU 數',
data: riskCount,
backgroundColor: rgba(palette.rust, 0.28),
borderColor: palette.rust,
borderWidth: 1,
maxBarThickness: 24,
yAxisID: 'y'
},
{
label: '平均價差 %',
data: avgGap,
type: 'line',
borderColor: palette.caramel,
backgroundColor: rgba(palette.caramel, 0.12),
borderWidth: 2.2,
tension: 0.32,
pointRadius: 2,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { position: isCompact() ? 'bottom' : 'top' },
tooltip: {
callbacks: {
label: ctx => {
const mode = ctx.dataset.yAxisID === 'y1' ? 'pct' : 'number';
return `${ctx.dataset.label}: ${formatMetric(ctx.parsed.y, mode)}`;
}
}
}
},
scales: {
x: { grid: { display: false }, ticks: { maxTicksLimit: isCompact() ? 5 : 10 } },
y: { beginAtZero: true, title: { display: !isCompact(), text: '風險 SKU 數' } },
y1: {
position: 'right',
grid: { drawOnChartArea: false },
...axisPercent('平均價差')
}
}
}
}));
}
// -- Marketing charts ----------------------------------------------
function renderMarketingBar(elId, marketing, color) {
const el = document.getElementById(elId);
@@ -606,6 +681,7 @@
renderDod();
renderWow();
renderTop10();
renderCompetitorGap();
const mk = dailySalesData.marketing || {};
if (mk.discount) renderMarketingBar('discountChart', mk.discount, palette.caramel);

View File

@@ -129,6 +129,7 @@
renderHtmlBars('momChart', data.labels, data.mom, { mode: 'pct' });
renderHtmlBars('aovChart', data.labels, data.aov, { mode: 'currency' });
renderHtmlBars('marginChart', data.labels, data.margin_rate, { mode: 'pct' });
renderHtmlBars('competitorPressureChart', data.labels, data.competitor_gap_pct, { mode: 'pct' });
}
function hasSeriesData(labels, ...seriesList) {
@@ -169,6 +170,7 @@
const momEl = document.getElementById('momChart');
const aovEl = document.getElementById('aovChart');
const marginEl = document.getElementById('marginChart');
const competitorEl = document.getElementById('competitorPressureChart');
if (!revenueEl || !momEl || !aovEl || !marginEl) return;
if (!hasSeriesData(data.labels, data.revenue, data.yoy)) {
@@ -331,6 +333,70 @@
}
}));
}
if (competitorEl) {
if (!hasSeriesData(data.labels, data.competitor_gap_pct, data.competitor_risk_count)) {
renderChartEmpty('competitorPressureChart', '目前沒有可用的 PChome 月度價差序列。');
} else {
rememberChart(new Chart(competitorEl, {
type: 'bar',
data: {
labels: data.labels,
datasets: [
{
label: '價格壓力 SKU 數',
data: data.competitor_risk_count || [],
backgroundColor: chartPalette.rustSoft,
borderColor: chartPalette.rust,
borderWidth: 1,
maxBarThickness: 34,
yAxisID: 'y'
},
{
label: '平均價差 (%)',
data: data.competitor_gap_pct || [],
type: 'line',
borderColor: chartPalette.honey,
borderWidth: 2,
yAxisID: 'y1',
tension: 0.34,
cubicInterpolationMode: 'monotone',
pointRadius: 2,
pointHoverRadius: 5
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { position: isCompact() ? 'bottom' : 'top' },
tooltip: {
callbacks: {
label: ctx => {
const mode = ctx.dataset.yAxisID === 'y1' ? 'pct' : 'number';
return `${ctx.dataset.label}: ${formatMetric(ctx.parsed.y, mode)}`;
}
}
}
},
scales: {
x: { grid: { display: false }, ticks: { maxTicksLimit: isCompact() ? 5 : 8 } },
y: {
beginAtZero: true,
title: { display: !isCompact(), text: '風險 SKU 數' }
},
y1: {
position: 'right',
grid: { drawOnChartArea: false },
...pctAxis('平均價差')
}
}
}
}));
}
}
stabilizeCharts();
}