498 lines
16 KiB
HTML
498 lines
16 KiB
HTML
{% extends 'ewoooc_base.html' %}
|
||
|
||
{% block title %}EwoooC 廠商缺貨{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.vendor-v2-stack {
|
||
display: grid;
|
||
gap: 24px;
|
||
}
|
||
|
||
.vendor-hero {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.6fr) minmax(280px, 0.8fr);
|
||
gap: 12px;
|
||
}
|
||
|
||
.vendor-hero-panel,
|
||
.vendor-card,
|
||
.vendor-table-card {
|
||
background: var(--momo-bg-surface);
|
||
border: 1px solid var(--momo-border-light);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.vendor-hero-panel {
|
||
display: flex;
|
||
min-height: 228px;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
padding: 24px;
|
||
}
|
||
|
||
.vendor-eyebrow {
|
||
display: inline-flex;
|
||
width: fit-content;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
padding: 4px 10px;
|
||
color: var(--momo-accent-strong);
|
||
background: var(--momo-accent-soft);
|
||
border-radius: var(--momo-radius-pill);
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.vendor-title {
|
||
margin: 0;
|
||
color: var(--momo-text-primary);
|
||
font-size: 34px;
|
||
font-weight: 800;
|
||
letter-spacing: 0;
|
||
line-height: 1.12;
|
||
}
|
||
|
||
.vendor-subtitle {
|
||
max-width: 680px;
|
||
margin: 10px 0 0;
|
||
color: var(--momo-text-secondary);
|
||
font-size: 14px;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.vendor-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 22px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.vendor-action {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 36px;
|
||
padding: 8px 14px;
|
||
color: var(--momo-text-primary);
|
||
background: var(--momo-bg-paper);
|
||
border: 1px solid var(--momo-border-light);
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
text-decoration: none;
|
||
transition: var(--momo-transition-base);
|
||
}
|
||
|
||
.vendor-action:hover {
|
||
color: var(--momo-text-primary);
|
||
border-color: var(--momo-border-strong);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.vendor-action.is-primary {
|
||
color: var(--momo-text-inverse);
|
||
background: var(--momo-ink);
|
||
border-color: var(--momo-ink);
|
||
}
|
||
|
||
.vendor-pulse {
|
||
display: grid;
|
||
align-content: stretch;
|
||
padding: 18px;
|
||
background: var(--momo-ink);
|
||
border: 1px solid var(--momo-ink);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.vendor-pulse-label {
|
||
color: rgba(250, 247, 240, 0.62);
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.vendor-pulse-value {
|
||
margin-top: 8px;
|
||
color: var(--momo-text-inverse);
|
||
font-size: 34px;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
}
|
||
|
||
.vendor-pulse-meta {
|
||
margin-top: 12px;
|
||
color: rgba(250, 247, 240, 0.7);
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.vendor-kpi-grid,
|
||
.vendor-flow-grid {
|
||
display: grid;
|
||
gap: 12px;
|
||
}
|
||
|
||
.vendor-kpi-grid {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
|
||
.vendor-flow-grid {
|
||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||
}
|
||
|
||
.vendor-card {
|
||
padding: 18px;
|
||
}
|
||
|
||
.vendor-kpi-label,
|
||
.vendor-card-label {
|
||
color: var(--momo-text-secondary);
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.vendor-kpi-value {
|
||
margin-top: 8px;
|
||
color: var(--momo-text-primary);
|
||
font-size: 30px;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
}
|
||
|
||
.vendor-kpi-sub {
|
||
margin-top: 8px;
|
||
color: var(--momo-text-tertiary);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.vendor-kpi-value.is-danger {
|
||
color: var(--momo-danger);
|
||
}
|
||
|
||
.vendor-kpi-value.is-success {
|
||
color: var(--momo-success);
|
||
}
|
||
|
||
.vendor-section-label {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.vendor-section-label .num {
|
||
color: var(--momo-text-tertiary);
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.vendor-section-label .title {
|
||
color: var(--momo-text-primary);
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.vendor-flow-card {
|
||
display: flex;
|
||
min-height: 138px;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
padding: 16px;
|
||
color: var(--momo-text-primary);
|
||
background: var(--momo-bg-surface);
|
||
border: 1px solid var(--momo-border-light);
|
||
border-radius: 8px;
|
||
text-decoration: none;
|
||
transition: var(--momo-transition-base);
|
||
}
|
||
|
||
.vendor-flow-card:hover {
|
||
color: var(--momo-text-primary);
|
||
border-color: var(--momo-border-strong);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.vendor-flow-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 34px;
|
||
height: 34px;
|
||
color: var(--momo-accent-strong);
|
||
background: var(--momo-accent-soft);
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.vendor-flow-title {
|
||
margin-top: 14px;
|
||
font-size: 14px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.vendor-flow-meta {
|
||
margin-top: 4px;
|
||
color: var(--momo-text-tertiary);
|
||
font-size: 11px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.vendor-table-card {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.vendor-table-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 14px 18px;
|
||
border-bottom: 1px solid var(--momo-border-light);
|
||
}
|
||
|
||
.vendor-table-title {
|
||
color: var(--momo-text-primary);
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.vendor-summary-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 0;
|
||
}
|
||
|
||
.vendor-summary-item {
|
||
min-height: 120px;
|
||
padding: 18px;
|
||
border-right: 1px solid var(--momo-border-light);
|
||
}
|
||
|
||
.vendor-summary-item:last-child {
|
||
border-right: 0;
|
||
}
|
||
|
||
.vendor-summary-title {
|
||
margin-top: 8px;
|
||
color: var(--momo-text-primary);
|
||
font-size: 16px;
|
||
font-weight: 800;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.vendor-summary-meta {
|
||
margin-top: 8px;
|
||
color: var(--momo-text-secondary);
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.vendor-empty {
|
||
padding: 28px;
|
||
color: var(--momo-text-secondary);
|
||
background: var(--momo-bg-paper);
|
||
border-top: 1px solid var(--momo-border-light);
|
||
font-size: 13px;
|
||
text-align: center;
|
||
}
|
||
|
||
@media (max-width: 1180px) {
|
||
.vendor-hero,
|
||
.vendor-kpi-grid,
|
||
.vendor-flow-grid,
|
||
.vendor-summary-grid {
|
||
grid-template-columns: 1fr 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 720px) {
|
||
.vendor-hero,
|
||
.vendor-kpi-grid,
|
||
.vendor-flow-grid,
|
||
.vendor-summary-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.vendor-title {
|
||
font-size: 28px;
|
||
}
|
||
|
||
.vendor-summary-item {
|
||
border-right: 0;
|
||
border-bottom: 1px solid var(--momo-border-light);
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
<div class="vendor-v2-stack">
|
||
<section class="vendor-hero" aria-label="廠商缺貨概況">
|
||
<div class="vendor-hero-panel">
|
||
<div>
|
||
<div class="vendor-eyebrow momo-mono">
|
||
<i class="fas fa-box-open"></i>
|
||
VENDOR STOCKOUT
|
||
</div>
|
||
<h1 class="vendor-title">廠商缺貨</h1>
|
||
<p class="vendor-subtitle">
|
||
以匯入的缺貨清單、廠商聯絡資料與郵件發送紀錄為準,追蹤待處理缺貨、已發送通知與聯絡資料完整度。
|
||
</p>
|
||
</div>
|
||
<div class="vendor-actions">
|
||
<a class="vendor-action is-primary" href="{{ url_for('vendor.import_page') }}">
|
||
<i class="fas fa-file-import"></i>
|
||
匯入 Excel
|
||
</a>
|
||
<a class="vendor-action" href="{{ url_for('vendor.list_page') }}">
|
||
<i class="fas fa-list-check"></i>
|
||
查看清單
|
||
</a>
|
||
<a class="vendor-action" href="{{ url_for('vendor.vendor_management_page') }}">
|
||
<i class="fas fa-address-book"></i>
|
||
廠商資料
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<aside class="vendor-pulse">
|
||
<div>
|
||
<div class="vendor-pulse-label momo-mono">最新批次</div>
|
||
{% if stats.latest_batch_id %}
|
||
<div class="vendor-pulse-value momo-mono">{{ stats.latest_batch_count | number_format }}</div>
|
||
<div class="vendor-pulse-meta momo-mono">
|
||
批次 {{ stats.latest_batch_id }}<br>
|
||
{{ stats.latest_batch_time.strftime('%Y-%m-%d %H:%M') if stats.latest_batch_time else '沒有時間紀錄' }}
|
||
</div>
|
||
{% else %}
|
||
<div class="vendor-pulse-value momo-mono">0</div>
|
||
<div class="vendor-pulse-meta momo-mono">目前沒有匯入批次紀錄</div>
|
||
{% endif %}
|
||
</div>
|
||
</aside>
|
||
</section>
|
||
|
||
<section aria-label="廠商缺貨指標">
|
||
<div class="vendor-section-label">
|
||
<span class="num momo-mono">01</span>
|
||
<span class="title">即時指標</span>
|
||
</div>
|
||
<div class="vendor-kpi-grid">
|
||
<div class="vendor-card">
|
||
<div class="vendor-kpi-label momo-mono">缺貨總筆數</div>
|
||
<div class="vendor-kpi-value momo-mono">{{ stats.total_stockouts | number_format }}</div>
|
||
<div class="vendor-kpi-sub momo-mono">涵蓋 {{ stats.distinct_vendor_count | number_format }} 個來源廠商</div>
|
||
</div>
|
||
<div class="vendor-card">
|
||
<div class="vendor-kpi-label momo-mono">待發送</div>
|
||
<div class="vendor-kpi-value momo-mono is-danger">{{ stats.pending_stockouts | number_format }}</div>
|
||
<div class="vendor-kpi-sub momo-mono">失敗 {{ stats.failed_stockouts | number_format }} 筆</div>
|
||
</div>
|
||
<div class="vendor-card">
|
||
<div class="vendor-kpi-label momo-mono">已發送</div>
|
||
<div class="vendor-kpi-value momo-mono is-success">{{ stats.sent_stockouts | number_format }}</div>
|
||
<div class="vendor-kpi-sub momo-mono">重複 {{ stats.duplicate_stockouts | number_format }} 筆</div>
|
||
</div>
|
||
<div class="vendor-card">
|
||
<div class="vendor-kpi-label momo-mono">啟用廠商</div>
|
||
<div class="vendor-kpi-value momo-mono">{{ stats.active_vendors | number_format }}</div>
|
||
<div class="vendor-kpi-sub momo-mono">啟用郵件 {{ stats.active_emails | number_format }}</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section aria-label="廠商缺貨工作流程">
|
||
<div class="vendor-section-label">
|
||
<span class="num momo-mono">02</span>
|
||
<span class="title">工作入口</span>
|
||
</div>
|
||
<div class="vendor-flow-grid">
|
||
<a class="vendor-flow-card" href="{{ url_for('vendor.import_page') }}">
|
||
<span class="vendor-flow-icon"><i class="fas fa-file-import"></i></span>
|
||
<span>
|
||
<span class="vendor-flow-title">Excel 匯入</span>
|
||
<span class="vendor-flow-meta momo-mono">建立缺貨批次</span>
|
||
</span>
|
||
</a>
|
||
<a class="vendor-flow-card" href="{{ url_for('vendor.list_page') }}">
|
||
<span class="vendor-flow-icon"><i class="fas fa-table-list"></i></span>
|
||
<span>
|
||
<span class="vendor-flow-title">缺貨清單</span>
|
||
<span class="vendor-flow-meta momo-mono">{{ stats.total_stockouts | number_format }} 筆記錄</span>
|
||
</span>
|
||
</a>
|
||
<a class="vendor-flow-card" href="{{ url_for('vendor.vendor_management_page') }}">
|
||
<span class="vendor-flow-icon"><i class="fas fa-address-card"></i></span>
|
||
<span>
|
||
<span class="vendor-flow-title">廠商管理</span>
|
||
<span class="vendor-flow-meta momo-mono">{{ stats.active_vendors | number_format }} 個啟用廠商</span>
|
||
</span>
|
||
</a>
|
||
<a class="vendor-flow-card" href="{{ url_for('vendor.send_email_page') }}">
|
||
<span class="vendor-flow-icon"><i class="fas fa-paper-plane"></i></span>
|
||
<span>
|
||
<span class="vendor-flow-title">郵件發送</span>
|
||
<span class="vendor-flow-meta momo-mono">{{ stats.pending_stockouts | number_format }} 筆待處理</span>
|
||
</span>
|
||
</a>
|
||
<a class="vendor-flow-card" href="{{ url_for('vendor.history_page') }}">
|
||
<span class="vendor-flow-icon"><i class="fas fa-clock-rotate-left"></i></span>
|
||
<span>
|
||
<span class="vendor-flow-title">發送歷史</span>
|
||
<span class="vendor-flow-meta momo-mono">{{ stats.email_total | number_format }} 筆郵件紀錄</span>
|
||
</span>
|
||
</a>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="vendor-table-card" aria-label="資料狀態摘要">
|
||
<div class="vendor-table-head">
|
||
<span class="vendor-table-title">資料狀態摘要</span>
|
||
<span class="vendor-kpi-sub momo-mono">來源:vendor_stockout / vendor_list / vendor_emails / email_send_log</span>
|
||
</div>
|
||
<div class="vendor-summary-grid">
|
||
<div class="vendor-summary-item">
|
||
<div class="vendor-card-label momo-mono">最新匯入</div>
|
||
{% if stats.latest_import_time %}
|
||
<div class="vendor-summary-title">{{ stats.latest_import_vendor or '未標記廠商' }}</div>
|
||
<div class="vendor-summary-meta momo-mono">
|
||
{{ stats.latest_import_time.strftime('%Y-%m-%d %H:%M') }}<br>
|
||
{{ stats.latest_import_product or '未標記商品' }}
|
||
</div>
|
||
{% else %}
|
||
<div class="vendor-summary-title">尚無匯入紀錄</div>
|
||
<div class="vendor-summary-meta momo-mono">資料庫目前沒有缺貨資料</div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="vendor-summary-item">
|
||
<div class="vendor-card-label momo-mono">郵件成功率</div>
|
||
<div class="vendor-summary-title momo-mono">{{ stats.email_success_rate }}%</div>
|
||
<div class="vendor-summary-meta momo-mono">
|
||
成功 {{ stats.email_success | number_format }} / 總計 {{ stats.email_total | number_format }}<br>
|
||
失敗 {{ stats.email_failed | number_format }},待發送 {{ stats.email_pending | number_format }}
|
||
</div>
|
||
</div>
|
||
<div class="vendor-summary-item">
|
||
<div class="vendor-card-label momo-mono">最新郵件</div>
|
||
{% if stats.latest_email_time %}
|
||
<div class="vendor-summary-title momo-mono">{{ stats.latest_email_status or '未標記狀態' }}</div>
|
||
<div class="vendor-summary-meta momo-mono">
|
||
{{ stats.latest_email_time.strftime('%Y-%m-%d %H:%M') }}<br>
|
||
vendor_id {{ stats.latest_email_vendor_id }}
|
||
</div>
|
||
{% else %}
|
||
<div class="vendor-summary-title">尚無郵件紀錄</div>
|
||
<div class="vendor-summary-meta momo-mono">email_send_log 目前沒有資料</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% if stats.total_stockouts == 0 %}
|
||
<div class="vendor-empty momo-mono">
|
||
目前沒有匯入的缺貨資料;請先使用既有 Excel 匯入流程建立真實批次。
|
||
</div>
|
||
{% endif %}
|
||
</section>
|
||
</div>
|
||
{% endblock %}
|