perf: 減輕活動看板首屏載入
All checks were successful
CD Pipeline / deploy (push) Successful in 1m3s

This commit is contained in:
OoO
2026-05-17 23:38:06 +08:00
parent ff49d31f73
commit 4736a7f7df
3 changed files with 130 additions and 19 deletions

View File

@@ -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;
}
}
</style>
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-edm.css') }}">
@@ -825,7 +861,7 @@
<div class="campaign-slot-tabs" role="tablist">
{% for slot, stats in slot_stats.items() %}
{% set slot_id = slugify(slot) %}
<a class="campaign-slot-tab {% if slot == active_tab %}active{% endif %}" id="slot-{{ slot_id }}-tab" href="{{ url_for(current_endpoint, slot=slot, sort_by=current_sort, order=current_order) }}" role="tab" aria-selected="{{ 'true' if slot == active_tab else 'false' }}">
<a class="campaign-slot-tab {% if slot == active_tab %}active{% endif %}" id="slot-{{ slot_id }}-tab" href="{{ url_for(current_endpoint, slot=slot, sort_by=current_sort, order=current_order, page=1, per_page=page_window.per_page) }}" role="tab" aria-selected="{{ 'true' if slot == active_tab else 'false' }}">
<span class="momo-mono">{{ slot }}</span>
<span class="campaign-slot-count momo-mono">{{ stats.get('on_shelf', 0) }} 件</span>
</a>
@@ -849,14 +885,27 @@
<div class="campaign-table-head">
<span class="momo-mono" style="font-size:11px;font-weight:800;color:var(--momo-text-tertiary);letter-spacing:.08em;">03</span>
<strong>商品列表</strong>
<span class="momo-mono" data-campaign-visible-count style="color:var(--momo-text-tertiary);font-size:12px;">{{ items|length }} 筆</span>
<span class="momo-mono" data-campaign-visible-count style="color:var(--momo-text-tertiary);font-size:12px;">
{{ page_window.start_item }}-{{ page_window.end_item }} / {{ page_window.total_items }} 筆
</span>
<div class="campaign-filterbar" aria-label="{{ slot }} 商品狀態篩選">
<button class="campaign-filter-chip is-active" type="button" data-campaign-filter="all">全部 {{ items|length }}</button>
<button class="campaign-filter-chip is-active" type="button" data-campaign-filter="all">本頁 {{ items|length }}</button>
<button class="campaign-filter-chip" type="button" data-campaign-filter="new">新品 {{ stats.get('new', 0) }}</button>
<button class="campaign-filter-chip" type="button" data-campaign-filter="up">漲價 {{ stats.get('up', 0) }}</button>
<button class="campaign-filter-chip" type="button" data-campaign-filter="down">降價 {{ stats.get('down', 0) }}</button>
<button class="campaign-filter-chip" type="button" data-campaign-filter="delisted">下架 {{ stats.get('delisted_last_run', 0) }}</button>
</div>
<nav class="campaign-pagination" aria-label="{{ slot }} 商品分頁">
<a class="campaign-page-link" href="{{ url_for(current_endpoint, slot=active_tab, sort_by=current_sort, order=current_order, page=page_window.page - 1, per_page=page_window.per_page) }}" aria-disabled="{{ 'false' if page_window.has_prev else 'true' }}">
<i class="fas fa-chevron-left" aria-hidden="true"></i>
<span>上一頁</span>
</a>
<span>第 {{ page_window.page }} / {{ page_window.total_pages }} 頁</span>
<a class="campaign-page-link" href="{{ url_for(current_endpoint, slot=active_tab, sort_by=current_sort, order=current_order, page=page_window.page + 1, per_page=page_window.per_page) }}" aria-disabled="{{ 'false' if page_window.has_next else 'true' }}">
<span>下一頁</span>
<i class="fas fa-chevron-right" aria-hidden="true"></i>
</a>
</nav>
</div>
<div class="campaign-table-wrap">
<table class="campaign-table">
@@ -865,16 +914,16 @@
<th>分類 / 狀態</th>
<th>
{% set next_order_name = 'asc' if current_sort == 'name' and current_order == 'desc' else 'desc' %}
<a href="{{ url_for(current_endpoint, slot=active_tab, sort_by='name', order=next_order_name) }}">商品資訊</a>
<a href="{{ url_for(current_endpoint, slot=active_tab, sort_by='name', order=next_order_name, page=1, per_page=page_window.per_page) }}">商品資訊</a>
</th>
<th class="text-end">
{% set next_order_price = 'asc' if current_sort == 'price' and current_order == 'desc' else 'desc' %}
<a href="{{ url_for(current_endpoint, slot=active_tab, sort_by='price', order=next_order_price) }}">價格</a>
<a href="{{ url_for(current_endpoint, slot=active_tab, sort_by='price', order=next_order_price, page=1, per_page=page_window.per_page) }}">價格</a>
</th>
<th class="text-center">
{% if current_promo_page == 'edm' %}
{% set next_order_qty = 'asc' if current_sort == 'remain_qty' and current_order == 'desc' else 'desc' %}
<a href="{{ url_for(current_endpoint, slot=active_tab, sort_by='remain_qty', order=next_order_qty) }}">銷售 / 庫存</a>
<a href="{{ url_for(current_endpoint, slot=active_tab, sort_by='remain_qty', order=next_order_qty, page=1, per_page=page_window.per_page) }}">銷售 / 庫存</a>
{% else %}
狀態
{% endif %}
@@ -1061,12 +1110,12 @@
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
let campaignPriceChartInstance = null;
let activeCampaignHistoryRange = 'month';
let currentCampaignICode = null;
let currentCampaignProductName = '';
let campaignChartLoader = null;
function getCSRFToken() {
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
@@ -1122,6 +1171,24 @@
}
}
function ensureCampaignChart() {
if (typeof Chart !== 'undefined') {
return Promise.resolve();
}
if (campaignChartLoader) {
return campaignChartLoader;
}
campaignChartLoader = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/chart.js';
script.async = true;
script.onload = resolve;
script.onerror = () => reject(new Error('Chart.js 載入失敗'));
document.head.appendChild(script);
});
return campaignChartLoader;
}
function updateCampaignHistoryRangeButtons() {
document.querySelectorAll('[data-campaign-history-range]').forEach(button => {
button.classList.toggle('is-active', button.dataset.campaignHistoryRange === activeCampaignHistoryRange);
@@ -1149,12 +1216,8 @@
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
modal.show();
if (typeof Chart === 'undefined') {
setCampaignHistoryChartState('圖表元件尚未載入完成,請重新整理後再試。');
return;
}
fetch(`/api/history/i-code/${encodeURIComponent(iCode)}?range=${activeCampaignHistoryRange}`)
ensureCampaignChart()
.then(() => fetch(`/api/history/i-code/${encodeURIComponent(iCode)}?range=${activeCampaignHistoryRange}`))
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);