feat(reports): move monthly analysis to v2 shell
All checks were successful
CD Pipeline / deploy (push) Successful in 2m14s
All checks were successful
CD Pipeline / deploy (push) Successful in 2m14s
This commit is contained in:
4
app.py
4
app.py
@@ -95,8 +95,8 @@ except Exception as e:
|
||||
sys_log.error(f"無法檢測磁碟空間: {e}")
|
||||
|
||||
# 🚩 系統版本定義 (備份與顯示用)
|
||||
# 🚩 2026-05-01 V10.75: Move AI recommendation page onto V2 shell
|
||||
SYSTEM_VERSION = "V10.75"
|
||||
# 🚩 2026-05-01 V10.76: Move monthly analysis report onto V2 shell
|
||||
SYSTEM_VERSION = "V10.76"
|
||||
|
||||
# ==========================================
|
||||
# 🔒 SQL Injection 防護函數
|
||||
|
||||
@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.75"
|
||||
SYSTEM_VERSION = "V10.76"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -63,7 +63,8 @@ def monthly_summary_analysis_page():
|
||||
"""月份總表數據分析展示頁 (Phase 9)"""
|
||||
return render_template('monthly_summary_analysis.html',
|
||||
datetime_now=datetime.now(TAIPEI_TZ).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
system_version=SYSTEM_VERSION)
|
||||
system_version=SYSTEM_VERSION,
|
||||
active_page='monthly')
|
||||
|
||||
|
||||
# ==========================================
|
||||
|
||||
@@ -1,48 +1,85 @@
|
||||
<!-- cspell:ignore MOMO datatables -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
{% extends 'ewoooc_base.html' %}
|
||||
{% block title %}月份總表數據分析 - WOOO TECH{% endblock %}
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>月份總表數據分析 - WOOO TECH</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">
|
||||
<!-- DataTables CSS -->
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.11.5/css/dataTables.bootstrap5.min.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background-color: #f4f6f9;
|
||||
padding-top: 75px;
|
||||
.monthly-analysis-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.navbar.bg-custom-dark {
|
||||
background: linear-gradient(135deg, #1f2937 0%, #374151 100%);
|
||||
.monthly-analysis-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 22px;
|
||||
border: 1px solid var(--momo-border-strong);
|
||||
border-radius: 8px;
|
||||
background:
|
||||
radial-gradient(circle at 18px 18px, rgba(42, 37, 32, 0.12) 1px, transparent 1px),
|
||||
linear-gradient(135deg, rgba(242, 178, 90, 0.22), rgba(255, 255, 255, 0.94) 46%, rgba(42, 37, 32, 0.06));
|
||||
background-size: 18px 18px, auto;
|
||||
box-shadow: var(--momo-shadow-soft);
|
||||
}
|
||||
|
||||
.monthly-analysis-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
color: var(--momo-text-strong);
|
||||
font-family: var(--momo-font-display);
|
||||
font-size: clamp(1.45rem, 2vw, 2.08rem);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.monthly-analysis-title i {
|
||||
color: var(--momo-warm-caramel) !important;
|
||||
}
|
||||
|
||||
.monthly-version-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
border: 1px solid rgba(42, 37, 32, 0.14);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
color: var(--momo-text-muted);
|
||||
font-family: var(--momo-font-mono);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--momo-border-subtle) !important;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--momo-shadow-soft);
|
||||
margin-bottom: 1.5rem;
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: rgba(250, 247, 240, 0.88) !important;
|
||||
border-bottom: 1px solid var(--momo-border-subtle);
|
||||
font-weight: 700;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
background:
|
||||
radial-gradient(circle at 14px 14px, rgba(42, 37, 32, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(135deg, rgba(42, 37, 32, 0.94), rgba(68, 54, 40, 0.9));
|
||||
background-size: 16px 16px, auto;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: var(--momo-shadow-medium);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
@@ -76,7 +113,7 @@
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border-left: 4px solid #4F46E5;
|
||||
border-left: 4px solid var(--momo-warm-caramel) !important;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
@@ -87,17 +124,14 @@
|
||||
.kpi-value {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 800;
|
||||
color: #1e293b;
|
||||
color: var(--momo-text-strong);
|
||||
font-family: var(--momo-font-mono);
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
.bg-custom-dark {
|
||||
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%) !important;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -111,21 +145,38 @@
|
||||
align-items: center;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
.monthly-analysis-page .table thead th,
|
||||
.monthly-analysis-page .table-light th {
|
||||
background: rgba(250, 247, 240, 0.96) !important;
|
||||
color: var(--momo-text-muted);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.monthly-analysis-hero {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block ewooo_content %}
|
||||
<div id="loadingOverlay" class="loading-overlay">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
</div>
|
||||
|
||||
{% include 'components/_navbar.html' %}
|
||||
|
||||
<div class="container-fluid px-4 mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="mb-0 fw-bold text-dark"><i class="fas fa-chart-pie me-2 text-primary"></i>月份總表數據分析</h3>
|
||||
<div class="text-muted small">系統版本: {{ system_version }}</div>
|
||||
</div>
|
||||
<div class="monthly-analysis-page">
|
||||
<section class="monthly-analysis-hero">
|
||||
<div>
|
||||
<h1 class="monthly-analysis-title"><i class="fas fa-chart-pie me-2 text-primary"></i>月份總表數據分析</h1>
|
||||
<p class="text-muted mb-0 mt-2">以資料庫 `monthly_summary_analysis` 的月結資料,檢視業績、毛利、YoY 與品牌/區域/價格帶結構。</p>
|
||||
</div>
|
||||
<div class="monthly-version-pill">
|
||||
<i class="fas fa-database"></i>
|
||||
<span>{{ system_version }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════ 進階篩選器 (對標業績分析) ═══════ -->
|
||||
<div class="filter-section">
|
||||
@@ -516,12 +567,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.11.5/js/dataTables.bootstrap5.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
|
||||
<script>
|
||||
let table;
|
||||
let compareChart = echarts.init(document.getElementById('compareChart'));
|
||||
@@ -1469,6 +1521,4 @@
|
||||
$(`#${tableId}`).parent().html(`<table class="table table-bordered table-hover table-sm text-center mb-0 small">${tableHtml}</table>`);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
@@ -228,6 +228,26 @@ def test_ai_recommend_uses_v2_shell_and_runtime_category_data():
|
||||
assert "@ai_bp.route('/api/ai/product_insights'" in route_source
|
||||
|
||||
|
||||
def test_monthly_summary_analysis_uses_v2_shell_and_real_monthly_api():
|
||||
template = (ROOT / "templates/monthly_summary_analysis.html").read_text(encoding="utf-8")
|
||||
route_source = (ROOT / "routes/monthly_routes.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "{% extends 'ewoooc_base.html' %}" in template
|
||||
assert "{% block ewooo_content %}" in template
|
||||
assert "{% block extra_css %}" in template
|
||||
assert "{% block extra_js %}" in template
|
||||
assert "components/_navbar.html" not in template
|
||||
assert "<!DOCTYPE html>" not in template
|
||||
assert "monthly-analysis-hero" in template
|
||||
assert "monthly-analysis-page" in template
|
||||
assert "/api/monthly_summary_data" in template
|
||||
assert "monthly_summary_analysis" in route_source
|
||||
assert "active_page='monthly'" in route_source
|
||||
assert "MonthlySummaryAnalysis" in route_source
|
||||
assert "mock" not in template.lower()
|
||||
assert "假商品" not in template
|
||||
|
||||
|
||||
def test_dashboard_v2_restores_real_price_history_chart():
|
||||
route_source = (ROOT / "routes/api_routes.py").read_text(encoding="utf-8")
|
||||
dashboard = (ROOT / "templates/dashboard_v2.html").read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user