Files
ewoooc/templates/growth_analysis.html
2026-05-06 22:04:17 +08:00

296 lines
12 KiB
HTML

{% extends 'ewoooc_base.html' %}
{% block title %}營運成長報表 - EwoooC{% endblock %}
{% block extra_css %}
<style>
.growth-analysis-page {
display: flex;
flex-direction: column;
gap: 18px;
}
.growth-analysis-page .card {
border: 1px solid var(--momo-border-strong);
border-radius: 8px;
box-shadow: var(--momo-shadow-soft);
margin-bottom: 1.5rem;
background: rgba(255, 253, 248, 0.94);
}
.growth-analysis-page .card-header {
background: rgba(250, 247, 240, 0.9);
border-bottom: 1px solid var(--momo-border-subtle);
font-weight: 800;
color: var(--momo-text-strong);
padding: 1rem 1.25rem;
}
.kpi-card { position: relative; overflow: hidden; border: none; }
.kpi-card .icon-bg { position: absolute; right: -15px; bottom: -15px; font-size: 6rem; opacity: 0.15; transform: rotate(-15deg); pointer-events: none; }
.kpi-value { font-size: 2rem; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 0.2rem; }
.kpi-label { font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; opacity: 0.9; }
.growth-analysis-page .bg-primary,
.growth-analysis-page .bg-success,
.growth-analysis-page .bg-info {
background: linear-gradient(135deg, var(--momo-page-accent-dark), var(--momo-page-accent)) !important;
}
.growth-analysis-page .bg-success {
background: linear-gradient(135deg, var(--momo-warm-earth), var(--momo-warm-honey)) !important;
}
.growth-analysis-page .bg-info {
background: linear-gradient(135deg, var(--momo-warm-rust), var(--momo-warm-caramel)) !important;
}
.growth-analysis-page .text-success,
.growth-analysis-page .trend-up {
color: var(--momo-warm-honey) !important;
}
.growth-analysis-page .trend-down {
color: var(--momo-warm-rust) !important;
}
</style>
{% endblock %}
{% block ewooo_content %}
<div class="growth-analysis-page">
{% include 'components/_analysis_report_tabs.html' %}
<div class="d-flex justify-content-between align-items-center mb-4 mt-4">
<h4 class="mb-0 fw-bold text-dark"><i class="fas fa-rocket me-2 text-success"></i>營運成長策略報表</h4>
<span class="text-muted small">數據更新至: {{ chart_data.labels[-1] if chart_data.labels else '-' }}</span>
</div>
<!-- KPI Cards -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card kpi-card bg-primary text-white h-100 shadow-sm">
<div class="card-body p-4">
<div class="kpi-label text-white-50">YTD 本年度累計業績 ({{ kpi.current_year }})</div>
<div class="kpi-value">${{ "{:,.0f}".format(kpi.ytd_revenue) }}</div>
<div class="mt-2">
<span class="badge bg-white text-primary me-2">YoY Growth</span>
<span class="fw-bold {{ 'text-white' if kpi.ytd_growth >= 0 else 'text-warning' }}">
<i class="fas fa-{{ 'arrow-up' if kpi.ytd_growth >= 0 else 'arrow-down' }} me-1"></i>
{{ "{:+.1f}%".format(kpi.ytd_growth) }}
</span>
<span class="small text-white-50 ms-1">vs 去年同期</span>
</div>
<i class="fas fa-chart-line icon-bg"></i>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card kpi-card bg-success text-white h-100 shadow-sm">
<div class="card-body p-4">
<div class="kpi-label text-white-50">近30天平均客單價 (AOV)</div>
<div class="kpi-value">${{ "{:,.0f}".format(kpi.recent_aov) }}</div>
<div class="mt-2 small text-white-50">
真實訂單基礎 (Unique Order ID)
</div>
<i class="fas fa-shopping-cart icon-bg"></i>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card kpi-card bg-info text-white h-100 shadow-sm">
<div class="card-body p-4">
<div class="kpi-label text-white-50">總訂單數 (Total Orders)</div>
<div class="kpi-value">{{ "{:,.0f}".format(kpi.total_orders) }}</div>
<div class="mt-2 small text-white-50">
全時期累計
</div>
<i class="fas fa-receipt icon-bg"></i>
</div>
</div>
</div>
</div>
<!-- Charts Row 1: Revenue & Growth -->
<div class="row mb-4">
<div class="col-lg-8">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-chart-bar me-2"></i>月營收與年增率 (Revenue & YoY)</span>
</div>
<div class="card-body">
<div style="height: 350px;">
<canvas id="revenueChart"></canvas>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100">
<div class="card-header">
<i class="fas fa-percentage me-2"></i>月增率分析 (MoM)
</div>
<div class="card-body">
<div style="height: 350px;">
<canvas id="momChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- Charts Row 2: AOV & Profit -->
<div class="row mb-4">
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">
<i class="fas fa-wallet me-2"></i>客單價趨勢 (AOV Trend)
</div>
<div class="card-body">
<div style="height: 300px;">
<canvas id="aovChart"></canvas>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">
<i class="fas fa-hand-holding-usd me-2"></i>獲利能力分析 (Gross Margin %)
</div>
<div class="card-body">
<div style="height: 300px;">
<canvas id="marginChart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.6/dist/chart.umd.min.js"></script>
<!-- Data Injection -->
<script id="chart-data" type="application/json">
{{ chart_data | tojson }}
</script>
<script>
const data = JSON.parse(document.getElementById('chart-data').textContent);
const chartPalette = {
caramel: 'rgba(201, 100, 66, 1)',
caramelSoft: 'rgba(201, 100, 66, 0.58)',
honey: 'rgba(184, 132, 22, 1)',
honeySoft: 'rgba(184, 132, 22, 0.58)',
rust: 'rgba(181, 52, 47, 1)',
rustSoft: 'rgba(181, 52, 47, 0.48)',
mahogany: 'rgba(143, 69, 48, 1)',
mahoganySoft: 'rgba(143, 69, 48, 0.12)'
};
Chart.defaults.color = '#6f665a';
Chart.defaults.borderColor = 'rgba(126, 111, 92, 0.18)';
Chart.defaults.font.family = "'Noto Sans TC', 'Inter', system-ui, sans-serif";
// 1. Revenue & YoY Chart (Mixed)
new Chart(document.getElementById('revenueChart'), {
type: 'bar',
data: {
labels: data.labels,
datasets: [
{
label: '月營收 ($)',
data: data.revenue,
backgroundColor: chartPalette.caramelSoft,
order: 2
},
{
label: 'YoY 年增率 (%)',
data: data.yoy,
type: 'line',
borderColor: chartPalette.rust,
borderWidth: 2,
yAxisID: 'y1',
order: 1,
tension: 0.3
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { beginAtZero: true, title: {display: true, text: '金額 ($)'} },
y1: {
position: 'right',
grid: { drawOnChartArea: false },
title: {display: true, text: '成長率 (%)'}
}
}
}
});
// 2. MoM Chart
new Chart(document.getElementById('momChart'), {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: 'MoM 月增率 (%)',
data: data.mom,
backgroundColor: (ctx) => {
const val = ctx.raw;
return val >= 0 ? chartPalette.honeySoft : chartPalette.rustSoft;
}
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } }
}
});
// 3. AOV Chart
new Chart(document.getElementById('aovChart'), {
type: 'line',
data: {
labels: data.labels,
datasets: [{
label: '平均客單價 ($)',
data: data.aov,
borderColor: chartPalette.caramel,
backgroundColor: 'rgba(201, 100, 66, 0.12)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true } }
}
});
// 4. Margin Rate Chart
new Chart(document.getElementById('marginChart'), {
type: 'line',
data: {
labels: data.labels,
datasets: [{
label: '毛利率 (%)',
data: data.margin_rate,
borderColor: chartPalette.honey,
backgroundColor: 'rgba(184, 132, 22, 0.12)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true } }
}
});
</script>
{% endblock %}