refactor(templates): 統一模板目錄並移除 fallback loader
ADR-017 Phase 3f-4:根目錄模板搬入 templates/,補 trends/login_history,移除 ChoiceLoader 根目錄 fallback,搬移 components,刪除 web/templates 下的空檔/死檔與 compose 舊模板 mount。
This commit is contained in:
353
templates/import.html
Normal file
353
templates/import.html
Normal file
@@ -0,0 +1,353 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user