diff --git a/config.py b/config.py index a61292b..c19ba3c 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.158" +SYSTEM_VERSION = "V10.159" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/routes/edm_routes.py b/routes/edm_routes.py index beb93d8..0320dab 100644 --- a/routes/edm_routes.py +++ b/routes/edm_routes.py @@ -6,6 +6,7 @@ EDM 與節慶促銷路由模組 """ import hashlib +import math from datetime import datetime, timezone, timedelta from flask import Blueprint, request, render_template, url_for from auth import login_required @@ -29,6 +30,8 @@ edm_bp = Blueprint('edm', __name__) _PROMO_DASHBOARD_CACHE = {} _PROMO_DASHBOARD_CACHE_MAX = 32 +_PROMO_PAGE_SIZE_DEFAULT = 120 +_PROMO_PAGE_SIZE_MAX = 200 # ========================================== @@ -100,6 +103,36 @@ def _remember_promo_dashboard_data(cache_key, data): _PROMO_DASHBOARD_CACHE[cache_key] = data +def _get_promo_page_window_args(): + """讀取促銷商品清單分頁參數,限制首屏 HTML 重量。""" + page = request.args.get('page', 1, type=int) or 1 + per_page = request.args.get('per_page', _PROMO_PAGE_SIZE_DEFAULT, type=int) or _PROMO_PAGE_SIZE_DEFAULT + return max(1, page), max(20, min(per_page, _PROMO_PAGE_SIZE_MAX)) + + +def _paginate_active_slot(data, page, per_page): + """只裁切目前顯示時段;其他統計仍保留完整資料。""" + active_tab = data.get('active_tab') + grouped_items = dict(data.get('sorted_grouped_items') or {}) + active_items = list(grouped_items.get(active_tab, [])) + total_items = len(active_items) + total_pages = max(1, math.ceil(total_items / per_page)) if total_items else 1 + page = min(max(1, page), total_pages) + start_idx = (page - 1) * per_page + end_idx = min(start_idx + per_page, total_items) + grouped_items[active_tab] = active_items[start_idx:end_idx] + return grouped_items, { + 'page': page, + 'per_page': per_page, + 'total_pages': total_pages, + 'total_items': total_items, + 'start_item': start_idx + 1 if total_items else 0, + 'end_item': end_idx, + 'has_prev': page > 1, + 'has_next': page < total_pages, + } + + def _build_promo_dashboard_data(session, page_type, page_name, sort_by, order, requested_slot=None): """ 通用的促銷儀表板數據建構函數 @@ -358,9 +391,11 @@ def edm_dashboard(): sort_by = request.args.get('sort_by', 'default') order = request.args.get('order', 'desc') requested_slot = request.args.get('slot') + page, per_page = _get_promo_page_window_args() try: data = _build_promo_dashboard_data(session, 'edm', '限時搶購', sort_by, order, requested_slot) + grouped_items, page_window = _paginate_active_slot(data, page, per_page) # 建立儀表板頁籤 promo_pages = [ @@ -380,8 +415,9 @@ def edm_dashboard(): promo_pages=promo_pages, current_promo_page='edm', page_title='MOMO 限時搶購', - grouped_items=data['sorted_grouped_items'], + grouped_items=grouped_items, slot_stats=data['slot_stats'], + page_window=page_window, total_edm_products=len(data['items_in_batch']), last_update=data['last_update_str'], activity_time=data['activity_time'], @@ -414,9 +450,11 @@ def festival_dashboard(): sort_by = request.args.get('sort_by', 'default') order = request.args.get('order', 'desc') requested_slot = request.args.get('slot') + page, per_page = _get_promo_page_window_args() try: data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot) + grouped_items, page_window = _paginate_active_slot(data, page, per_page) # 建立儀表板頁籤 promo_pages = [ @@ -435,8 +473,9 @@ def festival_dashboard(): promo_pages=promo_pages, current_promo_page='festival', page_title=PAGE_NAME, - grouped_items=data['sorted_grouped_items'], + grouped_items=grouped_items, slot_stats=data['slot_stats'], + page_window=page_window, total_edm_products=len(data['items_in_batch']), last_update=data['last_update_str'], activity_time=data['activity_time'], @@ -467,9 +506,11 @@ def mothers_day_dashboard(): sort_by = request.args.get('sort_by', 'default') order = request.args.get('order', 'desc') requested_slot = request.args.get('slot') + page, per_page = _get_promo_page_window_args() try: data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot) + grouped_items, page_window = _paginate_active_slot(data, page, per_page) # 建立儀表板頁籤 promo_pages = [ @@ -488,8 +529,9 @@ def mothers_day_dashboard(): promo_pages=promo_pages, current_promo_page='mothers_day', page_title=PAGE_NAME, - grouped_items=data['sorted_grouped_items'], + grouped_items=grouped_items, slot_stats=data['slot_stats'], + page_window=page_window, total_edm_products=len(data['items_in_batch']), last_update=data['last_update_str'], activity_time=data['activity_time'], @@ -520,9 +562,11 @@ def valentine_520_dashboard(): sort_by = request.args.get('sort_by', 'default') order = request.args.get('order', 'desc') requested_slot = request.args.get('slot') + page, per_page = _get_promo_page_window_args() try: data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot) + grouped_items, page_window = _paginate_active_slot(data, page, per_page) # 建立儀表板頁籤 promo_pages = [ @@ -541,8 +585,9 @@ def valentine_520_dashboard(): promo_pages=promo_pages, current_promo_page='valentine_520', page_title=PAGE_NAME, - grouped_items=data['sorted_grouped_items'], + grouped_items=grouped_items, slot_stats=data['slot_stats'], + page_window=page_window, total_edm_products=len(data['items_in_batch']), last_update=data['last_update_str'], activity_time=data['activity_time'], @@ -573,9 +618,11 @@ def labor_day_dashboard(): sort_by = request.args.get('sort_by', 'default') order = request.args.get('order', 'desc') requested_slot = request.args.get('slot') + page, per_page = _get_promo_page_window_args() try: data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot) + grouped_items, page_window = _paginate_active_slot(data, page, per_page) # 建立儀表板頁籤 promo_pages = [ @@ -594,8 +641,9 @@ def labor_day_dashboard(): promo_pages=promo_pages, current_promo_page='labor_day', page_title=PAGE_NAME, - grouped_items=data['sorted_grouped_items'], + grouped_items=grouped_items, slot_stats=data['slot_stats'], + page_window=page_window, total_edm_products=len(data['items_in_batch']), last_update=data['last_update_str'], activity_time=data['activity_time'], diff --git a/templates/edm_dashboard_v2.html b/templates/edm_dashboard_v2.html index 2329b3b..eba2aa4 100644 --- a/templates/edm_dashboard_v2.html +++ b/templates/edm_dashboard_v2.html @@ -412,6 +412,36 @@ background: var(--momo-ink); } + .campaign-pagination { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: auto; + color: var(--momo-text-secondary); + font-family: var(--momo-font-family-mono); + font-size: 11px; + font-weight: 800; + } + + .campaign-page-link { + display: inline-flex; + align-items: center; + gap: 5px; + min-height: 30px; + padding: 5px 9px; + color: var(--momo-text-primary); + background: var(--momo-bg-surface); + border: 1px solid var(--momo-border-light); + border-radius: 6px; + text-decoration: none; + } + + .campaign-page-link[aria-disabled="true"] { + pointer-events: none; + color: var(--momo-text-disabled); + background: var(--momo-bg-paper); + } + .campaign-table-wrap { overflow-x: auto; } @@ -708,6 +738,12 @@ .campaign-kpi-grid { grid-template-columns: 1fr 1fr; } + + .campaign-pagination { + width: 100%; + margin-left: 0; + justify-content: space-between; + } } @@ -825,7 +861,7 @@
{% for slot, stats in slot_stats.items() %} {% set slot_id = slugify(slot) %} - + {{ slot }} {{ stats.get('on_shelf', 0) }} 件 @@ -849,14 +885,27 @@
03 商品列表 - {{ items|length }} 筆 + + {{ page_window.start_item }}-{{ page_window.end_item }} / {{ page_window.total_items }} 筆 +
- +
+
@@ -865,16 +914,16 @@
分類 / 狀態 {% set next_order_name = 'asc' if current_sort == 'name' and current_order == 'desc' else 'desc' %} - 商品資訊 + 商品資訊 {% set next_order_price = 'asc' if current_sort == 'price' and current_order == 'desc' else 'desc' %} - 價格 + 價格 {% if current_promo_page == 'edm' %} {% set next_order_qty = 'asc' if current_sort == 'remain_qty' and current_order == 'desc' else 'desc' %} - 銷售 / 庫存 + 銷售 / 庫存 {% else %} 狀態 {% endif %} @@ -1061,12 +1110,12 @@ {% endblock %} {% block extra_js %} -