Some checks failed
CD Pipeline / deploy (push) Failing after 2m59s
P0-06: google_drive_service.py — pickle.load() 改 JSON token(消除 RCE 風險) P0-07: bot_api_routes.py:30 — BOT_API_TOKEN 移除硬編碼預設值 clawdbot_momo_2026 P0-08: auto_import_index.html — showAlert innerHTML 改 createTextNode(XSS 修復) P0-09: abc_analysis_detail.html + dashboard.html + daily_sales.html — Jinja2 | e 轉義 P0-10: openclaw_bot_routes.py:2634 — vendor PPT 補 return ppt_path(廠商報告恢復) P0-11: telegram_bot_service.py:177-214 — cmd_start/cmd_help 補 try/except P0-12: app.py:689-712 — 10 個 Blueprint 補齊 register(消滅 404 路由) P0-13: auto_heal_service.py — 實作 _write_heal_log(),AIOps 稽核閉環補完 P0-14: monitoring/prometheus.yml — 取消 alert_rules comment;新增 alert_rules.yml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
196 lines
9.4 KiB
HTML
196 lines
9.4 KiB
HTML
<!-- cspell:ignore MOMO datatables -->
|
|
<!DOCTYPE html>
|
|
<html lang="zh-TW">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>ABC 分析詳情 - {{ info.title }}</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.11.5/css/dataTables.bootstrap5.min.css">
|
|
<style>
|
|
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: #f4f6f9;
|
|
padding-top: 70px;
|
|
}
|
|
.card { border: none; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.03); margin-bottom: 1.5rem; background: #fff; }
|
|
.table th { font-weight: 600; color: #495057; background-color: #f8f9fa; }
|
|
.text-truncate-2 {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.navbar-dark.bg-primary {
|
|
background: linear-gradient(135deg, #4F46E5 0%, #6366F1 100%) !important;
|
|
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
|
|
}
|
|
|
|
.navbar-dark .navbar-brand {
|
|
color: #ffffff !important;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.navbar-dark .navbar-nav .nav-link {
|
|
color: rgba(255, 255, 255, 0.9) !important;
|
|
font-weight: 500;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.navbar-dark .navbar-nav .nav-link:hover {
|
|
color: #ffffff !important;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.navbar-dark .navbar-nav .nav-link.active {
|
|
color: #ffffff !important;
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-radius: 6px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.navbar-dark .navbar-text {
|
|
color: rgba(255, 255, 255, 0.8) !important;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-body-tertiary">
|
|
{% include 'components/_navbar.html' %}
|
|
|
|
<div class="container-fluid px-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-4 mt-4">
|
|
<div>
|
|
<h4 class="mb-1 fw-bold text-{{ info.color }}">
|
|
<i class="fas fa-layer-group me-2"></i>{{ info.title }}
|
|
</h4>
|
|
<span class="text-muted">{{ info.desc }}</span>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<!-- V-New: 動態補貨係數調整 -->
|
|
<div class="input-group input-group-sm" style="width: 200px;">
|
|
<span class="input-group-text bg-white">補貨係數 x</span>
|
|
<input type="number" id="restockFactor" class="form-control text-center" value="{{ current_factor }}" step="0.1" min="0">
|
|
<button class="btn btn-outline-primary" onclick="applyFactor()">應用</button>
|
|
</div>
|
|
|
|
<!-- V-Fix: 匯出連結需包含 factor 參數 -->
|
|
<a href="/api/export/excel/abc?class={{ target_class }}&factor={{ current_factor }}&{{ query_string }}" class="btn btn-success btn-sm">
|
|
<i class="fas fa-file-excel me-2"></i>匯出此類別報表
|
|
</a>
|
|
<button onclick="window.close()" class="btn btn-outline-secondary ms-2">關閉</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<table id="dataTable" class="table table-hover align-middle mb-0" style="width:100%">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center" style="width: 60px;">排名</th>
|
|
{% if cols.pid %}<th>商品ID</th>{% endif %}
|
|
<th style="width: 30%;">商品名稱</th>
|
|
{% if cols.brand %}<th>品牌</th>{% endif %}
|
|
{% if cols.vendor %}<th>廠商名稱</th>{% endif %}
|
|
{% if cols.cat %}<th>分類</th>{% endif %}
|
|
{% if cols.cost or cols.profit %}<th>毛利率</th>{% endif %}
|
|
{% if cols.qty %}<th>平均單價</th>{% endif %}
|
|
{% if cols.qty %}<th class="text-end">銷售數量</th>{% endif %}
|
|
{% if cols.qty %}<th class="text-end table-info">建議補貨 (x{{ current_factor }})</th>{% endif %}
|
|
<th class="text-end">銷售金額</th>
|
|
<th class="text-end">累積營收佔比</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in items %}
|
|
<tr>
|
|
<td class="text-center fw-bold text-muted">{{ loop.index }}</td>
|
|
{% if cols.pid %}
|
|
<td class="small text-muted">{{ item[cols.pid] }}</td>
|
|
{% endif %}
|
|
<td>
|
|
<div class="text-truncate-2" title="{{ item[cols.name] | e }}">
|
|
{{ item[cols.name] | e }}
|
|
</div>
|
|
</td>
|
|
{% if cols.brand %}
|
|
<td class="small text-muted">{{ item[cols.brand] }}</td>
|
|
{% endif %}
|
|
{% if cols.vendor %}
|
|
<td class="small text-muted">{{ item[cols.vendor] }}</td>
|
|
{% endif %}
|
|
{% if cols.cat %}
|
|
<td><span class="badge bg-light text-dark border">{{ item[cols.cat] }}</span></td>
|
|
{% endif %}
|
|
{% if cols.cost or cols.profit %}
|
|
<td>
|
|
{% set margin = item['calculated_margin_rate'] %}
|
|
<span class="{{ 'text-success' if margin >= 30 else ('text-danger' if margin < 10 else 'text-dark') }} fw-bold">
|
|
{{ "{:.1f}%".format(margin) }}
|
|
</span>
|
|
</td>
|
|
{% endif %}
|
|
{% if cols.qty %}
|
|
<td class="small">${{ "{:,.0f}".format(item['avg_unit_price']) }}</td>
|
|
{% endif %}
|
|
{% if cols.qty %}
|
|
<td class="text-end">{{ "{:,.0f}".format(item[cols.qty]) }}</td>
|
|
{% endif %}
|
|
{% if cols.qty %}
|
|
<td class="text-end table-info fw-bold">
|
|
{% if item['suggested_restock'] > 0 %}
|
|
{{ "{:,.0f}".format(item['suggested_restock']) }}
|
|
{% else %}
|
|
<span class="text-muted small">建議清倉</span>
|
|
{% endif %}
|
|
</td>
|
|
{% endif %}
|
|
<td class="text-end fw-bold text-danger">
|
|
${{ "{:,.0f}".format(item[cols.amount]) }}
|
|
</td>
|
|
<td class="text-end text-muted small">
|
|
{{ "{:.2f}%".format(item['cumulative_pct']) }}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
|
|
<script src="https://cdn.datatables.net/1.11.5/js/dataTables.bootstrap5.min.js"></script>
|
|
<script>
|
|
// V-Fix: 提取排序索引變數,避免在物件實字中混用模板語法導致編輯器誤判
|
|
const sortColIndex = parseInt("{{ sort_col_index }}");
|
|
|
|
$(document).ready(function() {
|
|
$('#dataTable').DataTable({
|
|
"language": {
|
|
"url": "//cdn.datatables.net/plug-ins/1.11.5/i18n/zh-HANT.json",
|
|
"paginate": {
|
|
"previous": "上一頁",
|
|
"next": "下一頁"
|
|
}
|
|
},
|
|
"pageLength": 25, // V-Adj: 預設每頁 25 筆,避免過長
|
|
"lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "全部"]], // V-New: 允許使用者選擇每頁筆數
|
|
"order": [[ sortColIndex, "desc" ]], // 預設按金額排序
|
|
"paging": true, // 確保分頁開啟
|
|
"info": true // 顯示 "第 x 至 x 筆,共 x 筆"
|
|
});
|
|
});
|
|
|
|
// V-New: 應用新的補貨係數
|
|
function applyFactor() {
|
|
const factor = document.getElementById('restockFactor').value;
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('factor', factor);
|
|
window.location.href = url.toString();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |