優化缺貨清單新版響應式介面
All checks were successful
CD Pipeline / deploy (push) Successful in 58s

This commit is contained in:
OoO
2026-05-13 20:14:59 +08:00
parent 8a7eed3505
commit 2e566ad186
3 changed files with 132 additions and 23 deletions

View File

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

View File

@@ -8,17 +8,17 @@
{% endblock %}
{% block ewooo_content %}
{% set status_labels = {'pending': '待發送', 'sent': '已發送', 'failed': '失敗'} %}
{% set status_labels = {'pending': '待發送', 'sent': '已發送', 'failed': '失敗', 'duplicate': '重複', 'unknown': '未標記'} %}
<div class="stockout-list-stack">
<section class="stockout-header">
<div>
<div class="stockout-eyebrow momo-mono">
<i class="fas fa-table-list"></i>STOCKOUT LIST
<i class="fas fa-table-list"></i>缺貨清單 · 即時狀態
</div>
<h1 class="stockout-title">缺貨清單</h1>
<p class="stockout-subtitle">
現有缺貨匯入資料呈現,可用批次、廠商、商品與發送狀態篩選;所有數字皆來自 vendor_stockout
正式缺貨資料呈現,可用批次、廠商、商品與發送狀態篩選;桌機保留密度,手機改為逐筆資料卡
</p>
</div>
<div class="stockout-actions">
@@ -31,6 +31,8 @@
</div>
</section>
<div class="momo-dot-divider is-tight is-whisper is-accent" aria-hidden="true"></div>
<section class="stockout-kpi-grid" aria-label="缺貨清單統計">
<div class="stockout-kpi">
<div class="stockout-kpi-label momo-mono">符合筆數</div>
@@ -62,6 +64,9 @@
<section class="stockout-filter-card" aria-label="缺貨清單篩選">
<form class="stockout-filter-form" method="GET" action="{{ url_for('vendor.list_page') }}">
<input type="hidden" name="ui" value="v2">
{% if current_status and current_status != 'all' %}
<input type="hidden" name="status" value="{{ current_status }}">
{% endif %}
<input class="stockout-input" type="search" name="q" value="{{ search_query }}" placeholder="搜尋廠商、商品名稱或編號">
<select class="stockout-select momo-mono" name="batch">
<option value="">全部批次</option>
@@ -77,6 +82,11 @@
<option value="vendor_asc" {% if current_sort == 'vendor_asc' %}selected{% endif %}>廠商代碼</option>
<option value="stockout_days_desc" {% if current_sort == 'stockout_days_desc' %}selected{% endif %}>缺貨天數</option>
</select>
<select class="stockout-select momo-mono" name="page_size" aria-label="每頁筆數">
{% for size in [30, 50, 100] %}
<option value="{{ size }}" {% if page_size == size %}selected{% endif %}>每頁 {{ size }} 筆</option>
{% endfor %}
</select>
<button class="stockout-action is-primary" type="submit">
<i class="fas fa-filter"></i>套用
</button>
@@ -85,15 +95,15 @@
<nav class="stockout-status-tabs" aria-label="缺貨狀態篩選">
<a class="stockout-tab {% if current_status == 'all' %}is-active{% endif %}"
href="{{ url_for('vendor.list_page', q=search_query, batch=current_batch, sort=current_sort) }}">全部</a>
href="{{ url_for('vendor.list_page', q=search_query, batch=current_batch, sort=current_sort, page_size=page_size) }}">全部</a>
<a class="stockout-tab {% if current_status == 'pending' %}is-active{% endif %}"
href="{{ url_for('vendor.list_page', status='pending', q=search_query, batch=current_batch, sort=current_sort) }}">待發送</a>
href="{{ url_for('vendor.list_page', status='pending', q=search_query, batch=current_batch, sort=current_sort, page_size=page_size) }}">待發送</a>
<a class="stockout-tab {% if current_status == 'sent' %}is-active{% endif %}"
href="{{ url_for('vendor.list_page', status='sent', q=search_query, batch=current_batch, sort=current_sort) }}">已發送</a>
href="{{ url_for('vendor.list_page', status='sent', q=search_query, batch=current_batch, sort=current_sort, page_size=page_size) }}">已發送</a>
<a class="stockout-tab {% if current_status == 'failed' %}is-active{% endif %}"
href="{{ url_for('vendor.list_page', status='failed', q=search_query, batch=current_batch, sort=current_sort) }}">失敗</a>
href="{{ url_for('vendor.list_page', status='failed', q=search_query, batch=current_batch, sort=current_sort, page_size=page_size) }}">失敗</a>
<a class="stockout-tab {% if current_status == 'duplicate' %}is-active{% endif %}"
href="{{ url_for('vendor.list_page', status='duplicate', q=search_query, batch=current_batch, sort=current_sort) }}">重複</a>
href="{{ url_for('vendor.list_page', status='duplicate', q=search_query, batch=current_batch, sort=current_sort, page_size=page_size) }}">重複</a>
</nav>
<section class="stockout-table-card" aria-label="缺貨資料表">
@@ -124,33 +134,34 @@
<tbody>
{% for record in records %}
{% set record_status = record.status or 'pending' %}
{% set status_class = record_status if record_status in status_labels else 'unknown' %}
<tr>
<td>
<span class="stockout-chip is-{{ record_status }} momo-mono">
<td data-label="狀態">
<span class="stockout-chip is-{{ status_class }} momo-mono">
{{ status_labels.get(record_status, record_status) }}
</span>
{% if record.is_duplicate %}
<div class="stockout-product-code momo-mono">重複 {{ record.duplicate_count or 0 }}</div>
{% endif %}
</td>
<td>
<td data-label="商品">
<div class="stockout-product-name" title="{{ record.product_name }}">{{ record.product_name }}</div>
<div class="stockout-product-code momo-mono">{{ record.product_code }}</div>
</td>
<td>
<td data-label="廠商">
<div class="stockout-vendor-name" title="{{ record.vendor_name }}">{{ record.vendor_name }}</div>
<div class="stockout-vendor-code momo-mono">{{ record.vendor_code }}</div>
</td>
<td class="momo-mono">{{ record.batch_id }}</td>
<td class="momo-mono">{{ record.current_stock if record.current_stock is not none else '—' }}</td>
<td class="momo-mono">{{ record.stockout_date.strftime('%Y-%m-%d') if record.stockout_date else '—' }}</td>
<td class="momo-mono">{{ record.stockout_days if record.stockout_days is not none else '—' }}</td>
<td class="momo-mono">
<td class="momo-mono" data-label="批次">{{ record.batch_id }}</td>
<td class="momo-mono" data-label="庫存">{{ record.current_stock if record.current_stock is not none else '—' }}</td>
<td class="momo-mono" data-label="缺貨日期">{{ record.stockout_date.strftime('%Y-%m-%d') if record.stockout_date else '—' }}</td>
<td class="momo-mono" data-label="缺貨天數">{{ record.stockout_days if record.stockout_days is not none else '—' }}</td>
<td class="momo-mono" data-label="30 日業績">
{% if record.monthly_sales_amount is not none %}
${{ record.monthly_sales_amount | int | number_format }}
{% else %}—{% endif %}
</td>
<td class="momo-mono">{{ record.created_at.strftime('%Y-%m-%d %H:%M') if record.created_at else '—' }}</td>
<td class="momo-mono" data-label="建立時間">{{ record.created_at.strftime('%Y-%m-%d %H:%M') if record.created_at else '—' }}</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -5,7 +5,8 @@
.stockout-list-stack {
display: grid;
gap: 22px;
min-width: 0;
gap: 18px;
}
.stockout-header {
@@ -46,6 +47,7 @@
}
.stockout-subtitle {
max-width: 720px;
margin: 8px 0 0;
color: var(--momo-text-secondary);
font-size: 13px;
@@ -98,6 +100,7 @@
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
min-width: 0;
}
.stockout-kpi,
@@ -108,7 +111,11 @@
border-radius: 8px;
}
.stockout-kpi { padding: 16px; }
.stockout-kpi {
min-width: 0;
padding: 16px;
box-shadow: inset 3px 0 0 var(--momo-border-strong);
}
.stockout-kpi-label {
color: var(--momo-text-secondary);
@@ -137,7 +144,7 @@
.stockout-filter-form {
display: grid;
grid-template-columns: minmax(220px, 1.4fr) minmax(180px, 0.9fr) minmax(160px, 0.8fr) auto;
grid-template-columns: minmax(220px, 1.6fr) minmax(180px, 0.9fr) minmax(160px, 0.8fr) minmax(130px, 0.62fr) auto;
gap: 10px;
align-items: center;
}
@@ -183,7 +190,10 @@
font-size: 11px;
}
.stockout-table-wrap { overflow-x: auto; }
.stockout-table-wrap {
max-width: 100%;
overflow-x: auto;
}
.stockout-table {
width: 100%;
@@ -256,6 +266,18 @@
border-color: rgba(214, 83, 68, 0.2);
}
.stockout-chip.is-duplicate {
color: var(--momo-warning-text);
background: var(--momo-tag-honey-bg);
border-color: var(--momo-tag-honey-border);
}
.stockout-chip.is-unknown {
color: var(--momo-text-tertiary);
background: var(--momo-bg-paper);
border-color: var(--momo-border-light);
}
.stockout-empty {
padding: 34px;
color: var(--momo-text-secondary);
@@ -358,6 +380,82 @@
padding: 12px;
}
.stockout-table-wrap {
overflow: visible;
}
.stockout-table {
min-width: 0;
}
.stockout-table,
.stockout-table thead,
.stockout-table tbody,
.stockout-table tr,
.stockout-table td {
display: block;
width: 100%;
}
.stockout-table thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.stockout-table tbody {
display: grid;
gap: 10px;
padding: 12px;
background: var(--momo-bg-paper);
}
.stockout-table tr {
background: var(--momo-bg-surface);
border: 1px solid var(--momo-border-light);
border-radius: 8px;
overflow: hidden;
}
.stockout-table td {
display: grid;
grid-template-columns: minmax(86px, 0.36fr) minmax(0, 1fr);
gap: 10px;
align-items: start;
padding: 10px 12px;
border-bottom: 1px solid var(--momo-border-light);
font-size: 12px;
}
.stockout-table td:last-child {
border-bottom: 0;
}
.stockout-table td::before {
content: attr(data-label);
color: var(--momo-text-tertiary);
font-family: var(--momo-font-mono);
font-size: 10px;
font-weight: 800;
}
.stockout-product-name,
.stockout-vendor-name {
max-width: 100%;
white-space: normal;
}
.stockout-pagination {
justify-content: stretch;
}
.stockout-page-link {
flex: 1 1 auto;
}
.stockout-title { font-size: 26px; }
}