Files
ewoooc/import.html
ogt 1b4f3a7bbe
Some checks failed
CD Pipeline / deploy (push) Failing after 59s
feat: EwoooC 初始化 — 完整專案推版至 Gitea
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml)
- 部署模式: rsync Python 檔案至 188 → docker restart (volume mount)
- Dockerfile/requirements 變動時自動重建 Docker image
- 部署通知: Telegram (開始/成功/失敗)
- 健康檢查: https://mo.wooo.work/health (最多 5 次重試)
- 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 01:21:13 +08:00

354 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Excel 匯入 - 廠商缺貨系統</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">
<style>
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
min-height: 100vh;
padding-top: 70px;
}
.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;
}
.navbar {
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%) !important;
}
.navbar.bg-custom-dark {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%) !important;
}
.upload-area {
border: 3px dashed #3498db;
border-radius: 20px;
padding: 3rem;
text-align: center;
background: white;
transition: all 0.3s ease;
cursor: pointer;
}
.upload-area:hover {
border-color: #2980b9;
background: #f8f9fa;
transform: scale(1.02);
}
.upload-area.dragover {
border-color: #27ae60;
background: #d5f4e6;
}
.upload-icon {
font-size: 5rem;
color: #3498db;
margin-bottom: 1rem;
}
.btn-upload {
padding: 1rem 3rem;
font-size: 1.1rem;
border-radius: 50px;
font-weight: 600;
}
.progress {
height: 30px;
border-radius: 15px;
}
.result-card {
border-left: 5px solid #3498db;
}
</style>
</head>
<body>
<!-- 導航列 -->
{% include 'components/_navbar.html' %}
<div class="container" style="padding: 2rem; max-width: 900px;">
<!-- 頁面標題 -->
<div class="card mb-4" style="border-radius: 16px;">
<div class="card-body">
<h2 class="mb-3">
<i class="fas fa-file-excel text-primary me-3"></i>Excel 匯入
</h2>
<p class="text-muted mb-0">上傳包含缺貨商品資料的 Excel 檔案,系統會自動檢測並匯入</p>
</div>
</div>
<!-- 上傳區域 -->
<div class="card mb-4" style="border-radius: 16px;">
<div class="card-body">
<div class="upload-area" id="uploadArea">
<input type="file" id="fileInput" accept=".xlsx,.xls" style="display: none;">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<h4 class="mb-3">拖曳檔案到此處或點擊選擇檔案</h4>
<p class="text-muted">支援格式: Excel (.xlsx, .xls)</p>
<button type="button" class="btn btn-primary btn-upload mt-3" id="selectFileBtn">
<i class="fas fa-folder-open me-2"></i>選擇檔案
</button>
</div>
<div class="mt-3 text-center">
<a href="/vendor-stockout/api/import/template" class="btn btn-outline-secondary">
<i class="fas fa-download me-2"></i>下載 Excel 範本
</a>
</div>
<!-- 檔案資訊 -->
<div id="fileInfo" class="mt-4" style="display: none;">
<div class="alert alert-info">
<i class="fas fa-file me-2"></i>
<strong>已選擇檔案:</strong> <span id="fileName"></span>
<span class="badge bg-primary ms-2" id="fileSize"></span>
</div>
<button type="button" class="btn btn-success w-100 btn-upload" id="uploadBtn">
<i class="fas fa-upload me-2"></i>開始匯入
</button>
</div>
<!-- 進度條 -->
<div id="progressArea" class="mt-4" style="display: none;">
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 100%">
匯入中...
</div>
</div>
</div>
</div>
</div>
<!-- 匯入結果 -->
<div id="resultArea" style="display: none;">
<div class="card result-card" style="border-radius: 16px;">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-check-circle text-success me-2"></i>匯入完成
</h5>
<div class="row mt-4">
<div class="col-md-3">
<div class="text-center">
<h3 class="text-primary" id="totalCount">0</h3>
<small class="text-muted">總筆數</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h3 class="text-success" id="successCount">0</h3>
<small class="text-muted">成功匯入</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h3 class="text-warning" id="duplicateCount">0</h3>
<small class="text-muted">重複項目</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h3 class="text-danger" id="failedCount">0</h3>
<small class="text-muted">失敗</small>
</div>
</div>
</div>
<div class="mt-4">
<p class="mb-2">
<i class="fas fa-barcode me-2"></i>
<strong>批次編號:</strong> <code id="batchId"></code>
</p>
</div>
<div class="mt-4 text-center">
<a href="/vendor-stockout/list" class="btn btn-primary me-2">
<i class="fas fa-list me-2"></i>查看缺貨清單
</a>
<button type="button" class="btn btn-outline-secondary" id="importAgainBtn">
<i class="fas fa-redo me-2"></i>再次匯入
</button>
</div>
</div>
</div>
</div>
<!-- 錯誤訊息 -->
<div id="errorArea" style="display: none;">
<div class="alert alert-danger">
<h5><i class="fas fa-exclamation-circle me-2"></i>匯入失敗</h5>
<p class="mb-0" id="errorMessage"></p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const selectFileBtn = document.getElementById('selectFileBtn');
const fileInfo = document.getElementById('fileInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const uploadBtn = document.getElementById('uploadBtn');
const progressArea = document.getElementById('progressArea');
const resultArea = document.getElementById('resultArea');
const errorArea = document.getElementById('errorArea');
const importAgainBtn = document.getElementById('importAgainBtn');
let selectedFile = null;
// 點擊上傳區域
uploadArea.addEventListener('click', () => {
fileInput.click();
});
selectFileBtn.addEventListener('click', (e) => {
e.stopPropagation();
fileInput.click();
});
// 檔案選擇
fileInput.addEventListener('change', (e) => {
handleFile(e.target.files[0]);
});
// 拖曳事件
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
handleFile(e.dataTransfer.files[0]);
});
// 處理檔案
function handleFile(file) {
if (!file) return;
if (!file.name.match(/\.(xlsx|xls)$/i)) {
alert('請選擇 Excel 檔案 (.xlsx 或 .xls)');
return;
}
selectedFile = file;
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
fileInfo.style.display = 'block';
resultArea.style.display = 'none';
errorArea.style.display = 'none';
}
// 上傳按鈕
uploadBtn.addEventListener('click', async () => {
if (!selectedFile) return;
const formData = new FormData();
formData.append('file', selectedFile);
// 取得 CSRF token
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fileInfo.style.display = 'none';
progressArea.style.display = 'block';
errorArea.style.display = 'none';
resultArea.style.display = 'none';
try {
const response = await fetch('/vendor-stockout/api/import/excel', {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken
},
body: formData
});
const result = await response.json();
progressArea.style.display = 'none';
if (result.success) {
// 顯示成功結果
document.getElementById('totalCount').textContent = result.data.total_count;
document.getElementById('successCount').textContent = result.data.success_count;
document.getElementById('duplicateCount').textContent = result.data.duplicate_count;
document.getElementById('failedCount').textContent = result.data.failed_count;
document.getElementById('batchId').textContent = result.data.batch_id;
resultArea.style.display = 'block';
} else {
// 顯示錯誤
document.getElementById('errorMessage').textContent = result.message;
errorArea.style.display = 'block';
}
} catch (error) {
progressArea.style.display = 'none';
document.getElementById('errorMessage').textContent = '網路錯誤: ' + error.message;
errorArea.style.display = 'block';
}
});
// 再次匯入
importAgainBtn.addEventListener('click', () => {
selectedFile = null;
fileInput.value = '';
fileInfo.style.display = 'none';
resultArea.style.display = 'none';
errorArea.style.display = 'none';
});
// 格式化檔案大小
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
</script>
</body>
</html>