refactor(templates): 統一模板目錄並移除 fallback loader
ADR-017 Phase 3f-4:根目錄模板搬入 templates/,補 trends/login_history,移除 ChoiceLoader 根目錄 fallback,搬移 components,刪除 web/templates 下的空檔/死檔與 compose 舊模板 mount。
This commit is contained in:
@@ -1,197 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WOOO TECH 品牌資產庫</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: #f8f9fa;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.asset-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 30px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.asset-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preview-box {
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* 棋盤格背景以顯示透明度 */
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%), linear-gradient(-45deg, #eee 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #eee 75%), linear-gradient(-45deg, transparent 75%, #eee 75%);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
||||
}
|
||||
|
||||
.preview-box img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.format-links a {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
text-decoration: none;
|
||||
color: #4F46E5;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid #4F46E5;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.format-links a:hover {
|
||||
background: #4F46E5;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>WOOO TECH 品牌資產庫</h1>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">1. 主品牌標誌 (Main Logo)</h2>
|
||||
<div class="asset-grid">
|
||||
<div class="asset-item">
|
||||
<div class="preview-box">
|
||||
<img src="/static/exports/WOOO_Main_Logo.svg" alt="Main Logo">
|
||||
</div>
|
||||
<div>
|
||||
<strong>SVG (向量)</strong><br>
|
||||
<div class="format-links">
|
||||
<a href="/static/exports/WOOO_Main_Logo.svg" download>下載 SVG</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset-item">
|
||||
<div class="preview-box">
|
||||
<img src="/static/exports/WOOO_Main_Logo.jpg" alt="Main Logo JPG">
|
||||
</div>
|
||||
<div>
|
||||
<strong>JPG (白底)</strong><br>
|
||||
<div class="format-links">
|
||||
<a href="/static/exports/WOOO_Main_Logo.jpg" download>下載 JPG</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 其他格式僅提供下載 -->
|
||||
<div class="asset-item">
|
||||
<div style="padding: 20px;">
|
||||
<strong>其他格式</strong><br>
|
||||
<div class="format-links">
|
||||
<a href="/static/exports/WOOO_Main_Logo.eps" download>EPS</a>
|
||||
<a href="/static/exports/WOOO_Main_Logo.pdf" download>PDF/AI</a>
|
||||
<a href="/static/exports/WOOO_Main_Logo.tiff" download>TIFF</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">2. 玻璃質感版 (Glass Version)</h2>
|
||||
<div class="asset-grid">
|
||||
<div class="asset-item">
|
||||
<div class="preview-box">
|
||||
<img src="/static/exports/WOOO_Glass_Logo.svg" alt="Glass Logo">
|
||||
</div>
|
||||
<div>
|
||||
<strong>SVG (向量)</strong><br>
|
||||
<div class="format-links">
|
||||
<a href="/static/exports/WOOO_Glass_Logo.svg" download>下載 SVG</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset-item">
|
||||
<div class="preview-box">
|
||||
<img src="/static/exports/WOOO_Glass_Logo.jpg" alt="Glass Logo JPG">
|
||||
</div>
|
||||
<div>
|
||||
<strong>JPG (白底)</strong><br>
|
||||
<div class="format-links">
|
||||
<a href="/static/exports/WOOO_Glass_Logo.jpg" download>下載 JPG</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">3. 能量流動版 (Gradient Version)</h2>
|
||||
<div class="asset-grid">
|
||||
<div class="asset-item">
|
||||
<div class="preview-box">
|
||||
<img src="/static/exports/WOOO_Gradient_Logo.svg" alt="Gradient Logo">
|
||||
</div>
|
||||
<div>
|
||||
<strong>SVG (向量)</strong><br>
|
||||
<div class="format-links">
|
||||
<a href="/static/exports/WOOO_Gradient_Logo.svg" download>下載 SVG</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset-item">
|
||||
<div class="preview-box">
|
||||
<img src="/static/exports/WOOO_Gradient_Logo.jpg" alt="Gradient Logo JPG">
|
||||
</div>
|
||||
<div>
|
||||
<strong>JPG (白底)</strong><br>
|
||||
<div class="format-links">
|
||||
<a href="/static/exports/WOOO_Gradient_Logo.jpg" download>下載 JPG</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,337 +0,0 @@
|
||||
{#
|
||||
WOOO 品牌載入動畫組件
|
||||
|
||||
使用方式:
|
||||
1. 在頁面中 include 此組件:
|
||||
{% include 'components/_loading.html' %}
|
||||
|
||||
2. 在 JavaScript 中控制顯示/隱藏:
|
||||
showLoading('正在載入數據...') // 顯示
|
||||
hideLoading() // 隱藏
|
||||
|
||||
3. 也可直接操作 DOM:
|
||||
document.getElementById('loadingOverlay').style.display = 'flex'; // 顯示
|
||||
document.getElementById('loadingOverlay').style.display = 'none'; // 隱藏
|
||||
#}
|
||||
|
||||
<!-- WOOO Loading Overlay -->
|
||||
<div id="loadingOverlay" class="wooo-loading-overlay">
|
||||
<div class="loading-logo-container">
|
||||
<!-- 脈衝光暈 -->
|
||||
<div class="logo-pulse"></div>
|
||||
<!-- 外層旋轉光環 -->
|
||||
<div class="logo-ring"></div>
|
||||
<!-- 內層反向旋轉光環 -->
|
||||
<div class="logo-ring-inner"></div>
|
||||
<!-- 環繞粒子 -->
|
||||
<div class="orbit-particles">
|
||||
<div class="orbit-particle"></div>
|
||||
<div class="orbit-particle"></div>
|
||||
<div class="orbit-particle"></div>
|
||||
<div class="orbit-particle"></div>
|
||||
</div>
|
||||
<!-- 閃爍星星 -->
|
||||
<div class="sparkles">
|
||||
<div class="sparkle"></div>
|
||||
<div class="sparkle"></div>
|
||||
<div class="sparkle"></div>
|
||||
<div class="sparkle"></div>
|
||||
<div class="sparkle"></div>
|
||||
<div class="sparkle"></div>
|
||||
</div>
|
||||
<!-- LOGO -->
|
||||
<div class="loading-logo">WOOO</div>
|
||||
</div>
|
||||
<div class="loading-text" id="loadingText">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i>正在載入數據...
|
||||
</div>
|
||||
<div class="loading-progress">
|
||||
<div class="loading-progress-bar"></div>
|
||||
</div>
|
||||
<div class="loading-hint" id="loadingHint">
|
||||
大量資料可能需要較長時間,請稍候
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* ============================================
|
||||
WOOO 品牌載入動畫樣式
|
||||
============================================ */
|
||||
|
||||
.wooo-loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(240, 245, 255, 0.95) 100%);
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* LOGO 動畫容器 */
|
||||
.wooo-loading-overlay .loading-logo-container {
|
||||
position: relative;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .loading-logo {
|
||||
z-index: 3;
|
||||
animation: wooo-cloud-float 3s ease-in-out infinite;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
letter-spacing: 4px;
|
||||
filter: drop-shadow(0 4px 15px rgba(79, 70, 229, 0.4));
|
||||
}
|
||||
|
||||
/* 雲端飄動動畫 */
|
||||
@keyframes wooo-cloud-float {
|
||||
0%, 100% {
|
||||
transform: translateY(0) translateX(0) scale(1);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-15px) translateX(5px) scale(1.02);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px) translateX(-3px) scale(1);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-20px) translateX(3px) scale(1.01);
|
||||
}
|
||||
}
|
||||
|
||||
/* 外層旋轉光環 */
|
||||
.wooo-loading-overlay .logo-ring {
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 4px solid transparent;
|
||||
border-top-color: #4F46E5;
|
||||
border-right-color: #7C3AED;
|
||||
border-radius: 50%;
|
||||
animation: wooo-ring-spin 2s linear infinite;
|
||||
}
|
||||
|
||||
/* 內層反向旋轉光環 */
|
||||
.wooo-loading-overlay .logo-ring-inner {
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 3px solid transparent;
|
||||
border-bottom-color: #EC4899;
|
||||
border-left-color: #F59E0B;
|
||||
border-radius: 50%;
|
||||
animation: wooo-ring-spin-reverse 1.5s linear infinite;
|
||||
}
|
||||
|
||||
/* 脈衝光暈 */
|
||||
.wooo-loading-overlay .logo-pulse {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: radial-gradient(circle, rgba(79, 70, 229, 0.3) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
animation: wooo-pulse-expand 2s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wooo-ring-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes wooo-ring-spin-reverse {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(-360deg); }
|
||||
}
|
||||
|
||||
@keyframes wooo-pulse-expand {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.8);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 環繞粒子 */
|
||||
.wooo-loading-overlay .orbit-particles {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
animation: wooo-orbit-rotate 4s linear infinite;
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .orbit-particle {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: linear-gradient(135deg, #4F46E5, #7C3AED);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 10px rgba(79, 70, 229, 0.8);
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .orbit-particle:nth-child(1) {
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .orbit-particle:nth-child(2) {
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .orbit-particle:nth-child(3) {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .orbit-particle:nth-child(4) {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
@keyframes wooo-orbit-rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 閃爍星星 */
|
||||
.wooo-loading-overlay .sparkles {
|
||||
position: absolute;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .sparkle {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #FCD34D;
|
||||
border-radius: 50%;
|
||||
animation: wooo-sparkle-twinkle 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .sparkle:nth-child(1) { top: 10%; left: 20%; animation-delay: 0s; }
|
||||
.wooo-loading-overlay .sparkle:nth-child(2) { top: 5%; right: 25%; animation-delay: 0.3s; }
|
||||
.wooo-loading-overlay .sparkle:nth-child(3) { bottom: 15%; right: 15%; animation-delay: 0.6s; }
|
||||
.wooo-loading-overlay .sparkle:nth-child(4) { bottom: 10%; left: 25%; animation-delay: 0.9s; }
|
||||
.wooo-loading-overlay .sparkle:nth-child(5) { top: 30%; left: 5%; animation-delay: 1.2s; }
|
||||
.wooo-loading-overlay .sparkle:nth-child(6) { top: 25%; right: 5%; animation-delay: 0.4s; }
|
||||
|
||||
@keyframes wooo-sparkle-twinkle {
|
||||
0%, 100% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 載入文字 */
|
||||
.wooo-loading-overlay .loading-text {
|
||||
font-size: 1.2rem;
|
||||
color: #4F46E5;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .loading-hint {
|
||||
font-size: 0.85rem;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* 進度條 */
|
||||
.wooo-loading-overlay .loading-progress {
|
||||
width: 200px;
|
||||
height: 4px;
|
||||
background: rgba(79, 70, 229, 0.2);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wooo-loading-overlay .loading-progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4F46E5, #7C3AED, #4F46E5);
|
||||
background-size: 200% 100%;
|
||||
animation: wooo-progress-flow 1.5s linear infinite;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@keyframes wooo-progress-flow {
|
||||
0% { background-position: 100% 0; }
|
||||
100% { background-position: -100% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* WOOO 載入動畫控制函數
|
||||
*/
|
||||
|
||||
// 顯示載入動畫
|
||||
function showLoading(text, hint) {
|
||||
const overlay = document.getElementById('loadingOverlay');
|
||||
const loadingText = document.getElementById('loadingText');
|
||||
const loadingHint = document.getElementById('loadingHint');
|
||||
|
||||
if (text && loadingText) {
|
||||
loadingText.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>' + text;
|
||||
}
|
||||
if (hint && loadingHint) {
|
||||
loadingHint.textContent = hint;
|
||||
}
|
||||
|
||||
if (overlay) {
|
||||
overlay.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
// 隱藏載入動畫
|
||||
function hideLoading() {
|
||||
const overlay = document.getElementById('loadingOverlay');
|
||||
if (overlay) {
|
||||
overlay.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新載入文字
|
||||
function updateLoadingText(text) {
|
||||
const loadingText = document.getElementById('loadingText');
|
||||
if (loadingText && text) {
|
||||
loadingText.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>' + text;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新載入提示
|
||||
function updateLoadingHint(hint) {
|
||||
const loadingHint = document.getElementById('loadingHint');
|
||||
if (loadingHint && hint) {
|
||||
loadingHint.textContent = hint;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,189 +0,0 @@
|
||||
{#
|
||||
導航列組件
|
||||
|
||||
注意:目前已停用登入驗證,所有選單項目對所有人可見。
|
||||
|
||||
使用方式:
|
||||
{% include 'components/_navbar.html' %}
|
||||
#}
|
||||
|
||||
<nav class="navbar navbar-expand-xl navbar-dark bg-custom-dark fixed-top shadow-sm">
|
||||
<div class="container">
|
||||
<!-- 品牌 Logo -->
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<span class="d-none d-sm-inline fw-bold" style="letter-spacing: 2px;">WOOO</span>
|
||||
</a>
|
||||
|
||||
<!-- 手機版選單按鈕 -->
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<!-- 商品看板 - 所有人可見 -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'dashboard' %}active{% endif %}" href="/">
|
||||
<i class="fas fa-chart-line me-1"></i>商品看板
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 活動看板 - 所有人可見 -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'edm' %}active{% endif %}" href="/edm">
|
||||
<i class="fas fa-bullhorn me-1"></i>活動看板
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 分析報表 -->
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if active_page in ['sales', 'daily_sales', 'monthly', 'growth'] %}active{% endif %}"
|
||||
href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-chart-bar me-1"></i>分析報表
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item" href="/sales_analysis">
|
||||
<i class="fas fa-chart-bar me-2"></i>業績分析
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/daily_sales">
|
||||
<i class="fas fa-calendar-day me-2"></i>當日業績
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/growth_analysis">
|
||||
<i class="fas fa-chart-line me-2"></i>成長分析
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monthly_summary_analysis">
|
||||
<i class="fas fa-table me-2"></i>月份總表數據分析
|
||||
</a>
|
||||
</li>
|
||||
{% if metabase_url %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ metabase_url }}" target="_blank">
|
||||
<i class="fas fa-chart-pie me-2"></i>自訂圖表 (Metabase)
|
||||
<i class="fas fa-external-link-alt ms-1 small text-muted"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if grist_url %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ grist_url }}" target="_blank">
|
||||
<i class="fas fa-table me-2"></i>資料協作 (Grist)
|
||||
<i class="fas fa-external-link-alt ms-1 small text-muted"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- 廠商缺貨 -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'vendor_stockout' %}active{% endif %}" href="/vendor-stockout">
|
||||
<i class="fas fa-box-open me-1"></i>廠商缺貨
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- AI 助手 -->
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if active_page in ['ai_recommend', 'ai_history'] %}active{% endif %}"
|
||||
href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-robot me-1"></i>AI 助手
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item" href="/ai_recommend">
|
||||
<i class="fas fa-magic me-2"></i>智慧文案生成
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/ai_history">
|
||||
<i class="fas fa-history me-2"></i>生成歷史記錄
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- 雲端匯入 -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'auto_import' %}active{% endif %}" href="/auto_import">
|
||||
<i class="fas fa-cloud-download-alt me-1"></i>雲端匯入
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 系統管理 -->
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if active_page in ['settings', 'system_settings', 'logs', 'crawler', 'user_management'] %}active{% endif %}"
|
||||
href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>系統管理
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item" href="/user_management">
|
||||
<i class="fas fa-users-cog me-2"></i>用戶管理
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/settings">
|
||||
<i class="fas fa-robot me-2"></i>爬蟲管理
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/crawler_management">
|
||||
<i class="fas fa-spider me-2"></i>爬蟲設定
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/system_settings">
|
||||
<i class="fas fa-sliders-h me-2"></i>系統設定
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/logs">
|
||||
<i class="fas fa-file-alt me-2"></i>系統日誌
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- 右側區域 -->
|
||||
<div class="d-flex align-items-center">
|
||||
<!-- 時間顯示 -->
|
||||
<span class="navbar-text text-light small me-3">
|
||||
<i class="fas fa-clock me-1"></i>
|
||||
<span id="navbar-datetime">{{ datetime_now or '' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 導航列時間更新腳本 -->
|
||||
<script>
|
||||
(function() {
|
||||
function updateNavbarTime() {
|
||||
const now = new Date();
|
||||
const formatted = now.getFullYear() + '-' +
|
||||
String(now.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(now.getDate()).padStart(2, '0') + ' ' +
|
||||
String(now.getHours()).padStart(2, '0') + ':' +
|
||||
String(now.getMinutes()).padStart(2, '0') + ':' +
|
||||
String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
const el = document.getElementById('navbar-datetime');
|
||||
if (el) el.textContent = formatted;
|
||||
}
|
||||
|
||||
// 每秒更新時間
|
||||
setInterval(updateNavbarTime, 1000);
|
||||
})();
|
||||
</script>
|
||||
@@ -1,250 +0,0 @@
|
||||
<!-- 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>
|
||||
Reference in New Issue
Block a user