This commit is contained in:
@@ -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 # 用於模板顯示
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user