perf: 收斂當日業績明細首屏
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s

This commit is contained in:
OoO
2026-05-18 00:08:50 +08:00
parent 32801b5d57
commit 53e9a57baf
4 changed files with 77 additions and 7 deletions

View File

@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.162"
SYSTEM_VERSION = "V10.163"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -12,7 +12,7 @@ import os
import pickle
from datetime import datetime, timezone, timedelta
from urllib.parse import quote
from flask import Blueprint, request, render_template, send_file
from flask import Blueprint, request, render_template, send_file, url_for
from auth import login_required
from sqlalchemy import inspect, text
import pandas as pd
@@ -46,6 +46,7 @@ _VIEW_CACHE_EXPIRY_SECONDS = 120
_SHARED_VIEW_CACHE_EXPIRY_SECONDS = 1800
_DAILY_SALES_VIEW_CACHE = {}
_DAILY_SALES_VIEW_CACHE_MAX = 24
_CATEGORY_TABLE_DEFAULT_LIMIT = 120
def _get_daily_view_cache(cache_key):
@@ -112,6 +113,15 @@ def _set_shared_daily_view_cache(cache_key, context):
pass
def _daily_sales_url_with_detail(detail_value):
args = request.args.to_dict(flat=True)
if detail_value == 'all':
args['detail'] = 'all'
else:
args.pop('detail', None)
return url_for('daily_sales.daily_sales', **args)
def clear_daily_sales_cache():
"""清除當日業績緩存(供匯入服務調用)"""
_clear_daily_sales_cache()
@@ -462,6 +472,8 @@ def prepare_category_summary(df, date_str=None, is_month_view=False, month_start
if 'profit' not in category_df.columns:
category_df['profit'] = 0
if 'revenue' in category_df.columns:
category_df = category_df.sort_values(by='revenue', ascending=False)
return category_df.to_dict('records')
@@ -514,6 +526,7 @@ def daily_sales():
is_month_view = not selected_date_param and not request.args.get('month')
if selected_month_param and not selected_date_param:
is_month_view = True
show_all_categories = request.args.get('detail') == 'all'
month_start = selected_month.replace(day=1)
month_end = (month_start + pd.DateOffset(months=1)) - pd.Timedelta(days=1)
@@ -531,6 +544,7 @@ def daily_sales():
selected_date.strftime('%Y-%m-%d'),
selected_month.strftime('%Y-%m'),
'month' if is_month_view else 'day',
'category_all' if show_all_categories else f'category_top{_CATEGORY_TABLE_DEFAULT_LIMIT}',
str(current_fingerprint),
])
cached_context = _get_daily_view_cache(view_cache_key) or _get_shared_daily_view_cache(view_cache_key)
@@ -601,6 +615,11 @@ def daily_sales():
month_start=month_start if is_month_view else None,
month_end=month_end if is_month_view else None
)
category_total_count = len(category_list)
if show_all_categories:
visible_categories = category_list
else:
visible_categories = category_list[:_CATEGORY_TABLE_DEFAULT_LIMIT]
calendar_data = prepare_calendar_data(df, selected_month)
marketing_data = prepare_marketing_summary(
df,
@@ -619,7 +638,13 @@ def daily_sales():
'month_kpi': month_kpi,
'is_month_view': is_month_view,
'chart_data': chart_data,
'categories': category_list,
'categories': visible_categories,
'category_total_count': category_total_count,
'category_visible_count': len(visible_categories),
'category_table_limit': _CATEGORY_TABLE_DEFAULT_LIMIT,
'category_show_all': show_all_categories,
'category_show_all_url': _daily_sales_url_with_detail('all'),
'category_limited_url': _daily_sales_url_with_detail(None),
'calendar_data': calendar_data,
'marketing_data': marketing_data,
'selected_month': selected_month.strftime('%Y-%m') if isinstance(selected_month, pd.Timestamp) else selected_month,

View File

@@ -332,14 +332,26 @@
{% endif %}
<!-- ============ Category Table ============ -->
{% set category_total = category_total_count|default(categories|length if categories else 0) %}
{% set category_visible = category_visible_count|default(categories|length if categories else 0) %}
<div class="row">
<div class="col-12">
<div class="card chart-card">
<header class="card-header card-header--split">
<span><i class="fas fa-table"></i> 分類業績明細</span>
<button type="button" class="btn btn-sm btn-export" onclick="exportCategoryTable()">
<i class="fas fa-file-excel"></i> 匯出 Excel
</button>
<span>
<i class="fas fa-table"></i> 分類業績明細
<small class="daily-table-count momo-mono">顯示 {{ category_visible }} / {{ category_total }} 筆</small>
</span>
<span class="daily-table-actions">
{% if category_show_all|default(false) and category_total > category_table_limit|default(120) %}
<a class="btn btn-sm btn-table-toggle" href="{{ category_limited_url }}">收合前 {{ category_table_limit|default(120) }} 筆</a>
{% elif category_total > category_visible %}
<a class="btn btn-sm btn-table-toggle" href="{{ category_show_all_url }}">顯示全部</a>
{% endif %}
<button type="button" class="btn btn-sm btn-export" onclick="exportCategoryTable()">
<i class="fas fa-file-excel"></i> 匯出 Excel
</button>
</span>
</header>
<div class="card-body">
<div class="chart-mobile-hint d-md-none">

View File

@@ -817,6 +817,39 @@
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.daily-table-count {
display: inline-flex;
margin-left: 0.5rem;
color: var(--momo-text-tertiary);
font-size: 0.76rem;
font-weight: 700;
}
.daily-table-actions {
display: inline-flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-table-toggle {
background: var(--momo-bg-surface);
color: var(--momo-text-secondary);
border: 1px solid var(--momo-border-light);
border-radius: 6px;
padding: 0.35rem 0.85rem;
font-weight: 700;
font-size: 0.82rem;
}
.btn-table-toggle:hover {
background: var(--momo-bg-paper);
color: var(--momo-text-primary);
border-color: var(--momo-page-accent-line);
}
.btn-export {