Files
ewoooc/templates/growth_analysis.html
OoO 53edcc0077 refactor(templates): 統一模板目錄並移除 fallback loader
ADR-017 Phase 3f-4:根目錄模板搬入 templates/,補 trends/login_history,移除 ChoiceLoader 根目錄 fallback,搬移 components,刪除 web/templates 下的空檔/死檔與 compose 舊模板 mount。
2026-04-29 21:44:38 +08:00

250 lines
10 KiB
HTML

<!-- cspell:ignore MOMO -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>營運成長報表 - MOMO 監控系統</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<style>
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: #f4f6f9; }
.navbar { box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
.card { border: none; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.03); margin-bottom: 1.5rem; transition: all 0.3s ease; background: #fff; }
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.08); }
.card-header { background-color: transparent; border-bottom: 1px solid rgba(0,0,0,0.05); font-weight: 700; color: #2c3e50; padding: 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; }
.trend-up { color: #2ecc71; }
.trend-down { color: #e74c3c; }
</style>
</head>
<body class="bg-body-tertiary">
{% include 'components/_navbar.html' %}
<div class="container-fluid px-4">
<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>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.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);
// 1. Revenue & YoY Chart (Mixed)
new Chart(document.getElementById('revenueChart'), {
type: 'bar',
data: {
labels: data.labels,
datasets: [
{
label: '月營收 ($)',
data: data.revenue,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
order: 2
},
{
label: 'YoY 年增率 (%)',
data: data.yoy,
type: 'line',
borderColor: '#ff6384',
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 ? 'rgba(75, 192, 192, 0.6)' : 'rgba(255, 99, 132, 0.6)';
}
}]
},
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: '#36a2eb',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
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: '#2ecc71',
backgroundColor: 'rgba(46, 204, 113, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true } }
}
});
</script>
</body>
</html>