Files
ewoooc/templates/vendor_stockout_vendor_management_v2.html
ogt fbce41cf02
All checks were successful
CD Pipeline / deploy (push) Successful in 1m3s
fix: align stockout pages with supply risk UX
2026-06-26 19:03:15 +08:00

839 lines
39 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "ewoooc_base.html" %}
{% block title %}供應商窗口 · EwoooC{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-vendor-tools.css') }}">
{% endblock %}
{% block content %}
<section class="vendor-tools-page" data-vendor-tool="management">
<header class="vendor-tools-header">
<div>
<span class="vendor-tools-kicker"><i class="fas fa-boxes-stacked"></i>供貨風險</span>
<h1>供應商窗口</h1>
<p>維護正確窗口,讓缺貨補救能快速送達。</p>
</div>
<div class="vendor-tools-nav" aria-label="廠商缺貨工具導覽">
<a href="/vendor-stockout/" ><i class="fas fa-table-columns"></i>總覽</a>
<a href="/vendor-stockout/list" ><i class="fas fa-list-check"></i>缺貨清單</a>
<a href="/vendor-stockout/import" ><i class="fas fa-file-arrow-up"></i>匯入</a>
<a href="/vendor-stockout/vendor-management" class="is-active" aria-current="page"><i class="fas fa-building"></i>廠商</a>
<a href="/vendor-stockout/send-email" ><i class="fas fa-paper-plane"></i>補貨通知</a>
<a href="/vendor-stockout/history" ><i class="fas fa-clock-rotate-left"></i>歷史</a>
</div>
</header>
<div class="vendor-tools-body">
<div class="container-fluid" style="padding: 2rem;">
<h2 class="mb-4">
<i class="fas fa-building me-2" style="color: var(--momo-page-accent-dark);"></i>供應商窗口
</h2>
<!-- 分頁標籤 -->
<div class="section-tabs">
<ul class="nav nav-tabs" id="vendorTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="list-tab" data-bs-toggle="tab" data-bs-target="#list" type="button">
<i class="fas fa-list me-2"></i>供應商清單
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="import-tab" data-bs-toggle="tab" data-bs-target="#import" type="button">
<i class="fas fa-file-upload me-2"></i>Excel 匯入
</button>
</li>
</ul>
</div>
<div class="tab-content" id="vendorTabContent">
<!-- 廠商清單 Tab -->
<div class="tab-pane fade show active" id="list" role="tabpanel">
<!-- 統計卡片 -->
<div class="row mb-4">
<div class="col-md-4">
<div class="stats-card">
<div class="stats-number text-primary" style="font-size: 2rem; font-weight: bold;" id="totalVendors">0</div>
<div class="stats-label text-muted">供應商數</div>
</div>
</div>
<div class="col-md-4">
<div class="stats-card">
<div class="stats-number" style="font-size: 2rem; font-weight: bold; color: var(--momo-page-accent-dark);" id="totalEmails">0</div>
<div class="stats-label text-muted">郵件地址總數</div>
</div>
</div>
<div class="col-md-4">
<div class="stats-card">
<div class="stats-number text-success" style="font-size: 2rem; font-weight: bold;" id="avgEmails">0</div>
<div class="stats-label text-muted">平均每廠商郵件數</div>
</div>
</div>
</div>
<!-- 篩選與操作 -->
<div class="card mb-3" style="border-radius: 12px;">
<div class="card-body">
<div class="row align-items-end">
<div class="col-md-4">
<label class="form-label">搜尋供應商</label>
<input type="text" class="form-control" id="searchVendor" placeholder="輸入供應商代碼或名稱">
</div>
<div class="col-md-3">
<button class="btn btn-success" id="searchBtn">
<i class="fas fa-search me-2"></i>搜尋
</button>
<button class="btn btn-outline-secondary" id="resetBtn">
<i class="fas fa-redo me-2"></i>重置
</button>
</div>
<div class="col-md-5 text-end">
<button class="btn btn-primary" id="addVendorBtn" style="background: var(--momo-page-accent-dark); border-color: var(--momo-page-accent-dark);">
<i class="fas fa-plus me-2"></i>新增廠商
</button>
</div>
</div>
</div>
</div>
<!-- 廠商列表 -->
<div class="table-container">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 150px;">廠商代碼</th>
<th>廠商名稱</th>
<th>郵件地址</th>
<th style="width: 120px;">郵件數量</th>
<th style="width: 180px;">操作</th>
</tr>
</thead>
<tbody id="vendorTable">
<tr>
<td colspan="5" class="text-center py-5">
<i class="fas fa-spinner fa-spin fa-2x text-muted mb-3"></i>
<p class="text-muted">載入中...</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 分頁 -->
<div class="d-flex justify-content-between align-items-center mt-4">
<div class="text-muted">
顯示 <span id="pageInfo">0-0 / 0</span>
</div>
<nav>
<ul class="pagination mb-0" id="pagination"></ul>
</nav>
</div>
</div>
<!-- Excel 匯入 Tab -->
<div class="tab-pane fade" id="import" role="tabpanel">
<div class="row justify-content-center">
<div class="col-md-10">
<!-- 上傳區域 -->
<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讓缺貨通知送到正確窗口。</p>
<p class="text-muted small">
<strong>必要資料:</strong>來源供應商編號、來源供應商名稱<br>
<strong>選填資料:</strong>Mail支援多個郵件用逗號或分號分隔
</p>
<button type="button" class="btn btn-primary btn-lg mt-3" id="selectFileBtn" style="background: var(--momo-page-accent-dark); border-color: var(--momo-page-accent-dark);">
<i class="fas fa-folder-open me-2"></i>選擇檔案
</button>
</div>
<div class="mt-3 text-center">
<a href="/vendor-stockout/api/vendor/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-lg" id="uploadBtn" style="background: var(--momo-page-accent-dark); border-color: var(--momo-page-accent-dark);">
<i class="fas fa-upload me-2"></i>開始匯入
</button>
</div>
<!-- 進度條 -->
<div id="progressArea" class="mt-4" style="display: none;">
<div class="progress" style="height: 30px; border-radius: 15px;">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 100%; background: var(--momo-page-accent-dark);">
匯入中...
</div>
</div>
</div>
</div>
</div>
<!-- 匯入結果 -->
<div id="resultArea" style="display: none;">
<div class="card" style="border-radius: 16px; border-left: 5px solid var(--momo-page-accent-dark);">
<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-info" id="updatedCount">0</h3>
<small class="text-muted">更新廠商</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h3 style="color: var(--momo-page-accent-dark);" id="emailCount">0</h3>
<small class="text-muted">郵件地址</small>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<div class="text-center">
<h3 class="text-danger" id="failedCount">0</h3>
<small class="text-muted">失敗</small>
</div>
</div>
</div>
<div class="mt-4 text-center">
<button type="button" class="btn btn-outline-secondary" id="importAgainBtn">
<i class="fas fa-redo me-2"></i>再次匯入
</button>
<button type="button" class="btn btn-primary" style="background: var(--momo-page-accent-dark); border-color: var(--momo-page-accent-dark);" onclick="refreshVendorList()">
<i class="fas fa-list 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>
</div>
</div>
</div>
</div>
<!-- 新增/編輯廠商 Modal -->
<div class="modal fade" id="vendorModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header" style="background: var(--momo-page-accent-dark); color: white;">
<h5 class="modal-title" id="vendorModalTitle">
<i class="fas fa-building me-2"></i>新增廠商
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="editVendorId">
<div class="mb-3">
<label class="form-label">廠商代碼 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="vendorCode" placeholder="輸入廠商代碼">
</div>
<div class="mb-3">
<label class="form-label">廠商名稱 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="vendorName" placeholder="輸入廠商名稱">
</div>
<div class="mb-3">
<label class="form-label">郵件地址 <small class="text-muted">(每行一個郵件地址)</small></label>
<textarea class="form-control" id="vendorEmails" rows="5" placeholder="輸入郵件地址,每行一個"></textarea>
<small class="text-muted">可貼上多個收件人,讓缺貨通知一次送到正確窗口。</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveVendorBtn" style="background: var(--momo-page-accent-dark); border-color: var(--momo-page-accent-dark);">
<i class="fas fa-save me-2"></i>儲存
</button>
</div>
</div>
</div>
</div>
<!-- 管理郵件 Modal -->
<div class="modal fade" id="emailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header" style="background: var(--momo-page-accent-dark); color: white;">
<h5 class="modal-title">
<i class="fas fa-envelope me-2"></i>管理郵件地址
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="emailVendorCode">
<h6 class="mb-3">
<strong id="emailVendorName"></strong> (<span id="emailVendorCode2"></span>)
</h6>
<!-- 新增郵件 -->
<div class="card mb-3">
<div class="card-body">
<h6><i class="fas fa-plus-circle me-2"></i>新增郵件地址</h6>
<div class="input-group">
<input type="email" class="form-control" id="newEmail" placeholder="輸入新的郵件地址">
<button class="btn btn-success" id="addEmailBtn">
<i class="fas fa-plus me-1"></i>新增
</button>
</div>
</div>
</div>
<!-- 現有郵件列表 -->
<div class="card">
<div class="card-body">
<h6><i class="fas fa-list me-2"></i>現有郵件地址</h6>
<div id="emailList" class="mt-3">
<p class="text-center text-muted">載入中...</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">關閉</button>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
// ====== Global Variables ======
const vendorCsrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
let currentPage = 1;
let pageSize = 20;
let totalVendors = 0;
let searchKeyword = '';
let selectedFile = null;
let vendorModal, emailModal;
// ====== Initialize ======
document.addEventListener('DOMContentLoaded', () => {
vendorModal = new bootstrap.Modal(document.getElementById('vendorModal'));
emailModal = new bootstrap.Modal(document.getElementById('emailModal'));
loadVendorList();
bindEvents();
});
// ====== Bind Events ======
function bindEvents() {
// 廠商清單相關
document.getElementById('searchBtn').addEventListener('click', () => {
searchKeyword = document.getElementById('searchVendor').value;
currentPage = 1;
loadVendorList();
});
document.getElementById('resetBtn').addEventListener('click', () => {
document.getElementById('searchVendor').value = '';
searchKeyword = '';
currentPage = 1;
loadVendorList();
});
document.getElementById('addVendorBtn').addEventListener('click', () => {
openVendorModal();
});
document.getElementById('saveVendorBtn').addEventListener('click', saveVendor);
// 郵件管理相關
document.getElementById('addEmailBtn').addEventListener('click', addEmail);
// Excel 匯入相關
bindImportEvents();
}
// ====== Vendor List Functions ======
async function loadVendorList() {
try {
const params = new URLSearchParams({
page: currentPage,
page_size: pageSize
});
if (searchKeyword) {
params.append('search', searchKeyword);
}
const response = await fetch(`/vendor-stockout/api/vendor/list?${params}`);
const result = await response.json();
if (result.success) {
renderVendorTable(result.data.vendors);
updateVendorStats(result.data.stats);
updatePagination(result.data.total);
totalVendors = result.data.total;
}
} catch (error) {
console.error('載入廠商清單失敗:', error);
document.getElementById('vendorTable').innerHTML = `
<tr>
<td colspan="5" class="text-center py-5 text-danger">
<i class="fas fa-exclamation-circle fa-2x mb-3"></i>
<p>載入失敗: ${error.message}</p>
</td>
</tr>
`;
}
}
function renderVendorTable(vendors) {
const tbody = document.getElementById('vendorTable');
if (vendors.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="5" class="text-center py-5">
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<p class="text-muted">暫無廠商資料</p>
</td>
</tr>
`;
return;
}
tbody.innerHTML = vendors.map(vendor => `
<tr>
<td><strong>${vendor.vendor_code}</strong></td>
<td>${vendor.vendor_name}</td>
<td>
${vendor.emails.slice(0, 2).map(email =>
`<span class="email-badge">${email}</span>`
).join('')}
${vendor.email_count > 2 ? `<span class="badge bg-secondary">+${vendor.email_count - 2} 個</span>` : ''}
</td>
<td class="text-center">
<span class="badge bg-info">${vendor.email_count} 個</span>
</td>
<td>
<button class="btn btn-sm btn-outline-primary me-1" onclick="editVendor('${vendor.vendor_code}')">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-outline-info me-1" onclick="manageEmails('${vendor.vendor_code}', '${vendor.vendor_name}')">
<i class="fas fa-envelope"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteVendor('${vendor.vendor_code}', '${vendor.vendor_name}')">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`).join('');
}
function updateVendorStats(stats) {
document.getElementById('totalVendors').textContent = stats.total_vendors || 0;
document.getElementById('totalEmails').textContent = stats.total_emails || 0;
document.getElementById('avgEmails').textContent = stats.avg_emails || '0.0';
}
function updatePagination(total) {
const totalPages = Math.ceil(total / pageSize);
const start = (currentPage - 1) * pageSize + 1;
const end = Math.min(currentPage * pageSize, total);
document.getElementById('pageInfo').textContent = `${start}-${end} / ${total}`;
const pagination = document.getElementById('pagination');
let html = '';
html += `<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="changePage(${currentPage - 1}); return false;">上一頁</a>
</li>`;
for (let i = 1; i <= totalPages; i++) {
if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) {
html += `<li class="page-item ${i === currentPage ? 'active' : ''}">
<a class="page-link" href="#" onclick="changePage(${i}); return false;">${i}</a>
</li>`;
} else if (i === currentPage - 3 || i === currentPage + 3) {
html += `<li class="page-item disabled"><a class="page-link">...</a></li>`;
}
}
html += `<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="changePage(${currentPage + 1}); return false;">下一頁</a>
</li>`;
pagination.innerHTML = html;
}
function changePage(page) {
const totalPages = Math.ceil(totalVendors / pageSize);
if (page < 1 || page > totalPages) return;
currentPage = page;
loadVendorList();
}
// ====== Vendor CRUD Functions ======
function openVendorModal(vendorCode = null) {
document.getElementById('editVendorId').value = vendorCode || '';
document.getElementById('vendorModalTitle').innerHTML = vendorCode
? '<i class="fas fa-edit me-2"></i>編輯廠商'
: '<i class="fas fa-plus me-2"></i>新增廠商';
if (vendorCode) {
loadVendorDetail(vendorCode);
} else {
document.getElementById('vendorCode').value = '';
document.getElementById('vendorCode').readOnly = false;
document.getElementById('vendorName').value = '';
document.getElementById('vendorEmails').value = '';
}
vendorModal.show();
}
async function loadVendorDetail(vendorCode) {
try {
const response = await fetch(`/vendor-stockout/api/vendor/${vendorCode}`);
const result = await response.json();
if (result.success) {
const vendor = result.data;
document.getElementById('vendorCode').value = vendor.vendor_code;
document.getElementById('vendorCode').readOnly = true;
document.getElementById('vendorName').value = vendor.vendor_name;
document.getElementById('vendorEmails').value = vendor.emails.join('\n');
}
} catch (error) {
alert('載入廠商資料失敗: ' + error.message);
}
}
async function editVendor(vendorCode) {
openVendorModal(vendorCode);
}
async function saveVendor() {
const vendorCode = document.getElementById('vendorCode').value.trim();
const vendorName = document.getElementById('vendorName').value.trim();
const emailsText = document.getElementById('vendorEmails').value;
if (!vendorCode || !vendorName) {
alert('廠商代碼和名稱必須填寫!');
return;
}
const emails = emailsText.split('\n')
.map(e => e.trim())
.filter(e => e && e.includes('@'));
const data = {
vendor_code: vendorCode,
vendor_name: vendorName,
emails: emails
};
try {
const isEdit = document.getElementById('editVendorId').value;
const url = isEdit
? `/vendor-stockout/api/vendor/${vendorCode}`
: '/vendor-stockout/api/vendor';
const method = isEdit ? 'PUT' : 'POST';
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': vendorCsrfToken
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
vendorModal.hide();
await loadVendorList();
alert(isEdit ? '更新成功!' : '新增成功!');
} else {
alert(result.message || '操作失敗');
}
} catch (error) {
alert('操作失敗: ' + error.message);
}
}
async function deleteVendor(vendorCode, vendorName) {
if (!confirm(`確定要刪除廠商「${vendorName}」嗎?這將同時刪除所有相關郵件地址。`)) return;
try {
const response = await fetch(`/vendor-stockout/api/vendor/${vendorCode}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': vendorCsrfToken
}
});
const result = await response.json();
if (result.success) {
await loadVendorList();
alert('刪除成功!');
} else {
alert(result.message || '刪除失敗');
}
} catch (error) {
alert('刪除失敗: ' + error.message);
}
}
// ====== Email Management Functions ======
async function manageEmails(vendorCode, vendorName) {
document.getElementById('emailVendorCode').value = vendorCode;
document.getElementById('emailVendorCode2').textContent = vendorCode;
document.getElementById('emailVendorName').textContent = vendorName;
document.getElementById('newEmail').value = '';
await loadEmailList(vendorCode);
emailModal.show();
}
async function loadEmailList(vendorCode) {
try {
const response = await fetch(`/vendor-stockout/api/vendor/${vendorCode}/emails`);
const result = await response.json();
if (result.success) {
const emailList = document.getElementById('emailList');
if (result.data.length === 0) {
emailList.innerHTML = '<p class="text-muted text-center">暫無郵件地址</p>';
} else {
emailList.innerHTML = result.data.map(email => `
<div class="d-flex justify-content-between align-items-center mb-2 p-2 border rounded">
<div>
<i class="fas fa-envelope me-2" style="color: var(--momo-page-accent-dark);"></i>
<strong>${email.email}</strong>
<span class="badge bg-secondary ms-2">${email.email_type}</span>
</div>
<button class="btn btn-sm btn-outline-danger" onclick="deleteEmail(${email.id})">
<i class="fas fa-trash"></i>
</button>
</div>
`).join('');
}
}
} catch (error) {
document.getElementById('emailList').innerHTML =
'<p class="text-danger text-center">載入失敗: ' + error.message + '</p>';
}
}
async function addEmail() {
const vendorCode = document.getElementById('emailVendorCode').value;
const email = document.getElementById('newEmail').value.trim();
if (!email) {
alert('先填郵件地址。');
return;
}
if (!email.includes('@')) {
alert('郵件地址格式不正確。');
return;
}
try {
const response = await fetch(`/vendor-stockout/api/vendor/${vendorCode}/emails`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': vendorCsrfToken
},
body: JSON.stringify({ email: email, email_type: 'primary' })
});
const result = await response.json();
if (result.success) {
document.getElementById('newEmail').value = '';
await loadEmailList(vendorCode);
await loadVendorList();
alert('新增成功!');
} else {
alert(result.message || '新增失敗');
}
} catch (error) {
alert('新增失敗: ' + error.message);
}
}
async function deleteEmail(emailId) {
if (!confirm('確定要刪除這個郵件地址嗎?')) return;
const vendorCode = document.getElementById('emailVendorCode').value;
try {
const response = await fetch(`/vendor-stockout/api/vendor/email/${emailId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': vendorCsrfToken
}
});
const result = await response.json();
if (result.success) {
await loadEmailList(vendorCode);
await loadVendorList();
alert('刪除成功!');
} else {
alert(result.message || '刪除失敗');
}
} catch (error) {
alert('刪除失敗: ' + error.message);
}
}
// ====== Excel Import Functions ======
function bindImportEvents() {
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const selectFileBtn = document.getElementById('selectFileBtn');
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]);
});
document.getElementById('uploadBtn').addEventListener('click', uploadExcel);
document.getElementById('importAgainBtn').addEventListener('click', resetImport);
}
function handleFile(file) {
if (!file) return;
if (!file.name.match(/\.(xlsx|xls)$/i)) {
alert('先選擇供應商窗口 Excel 檔。');
return;
}
selectedFile = file;
document.getElementById('fileName').textContent = file.name;
document.getElementById('fileSize').textContent = formatFileSize(file.size);
document.getElementById('fileInfo').style.display = 'block';
document.getElementById('resultArea').style.display = 'none';
document.getElementById('errorArea').style.display = 'none';
}
async function uploadExcel() {
if (!selectedFile) return;
const formData = new FormData();
formData.append('file', selectedFile);
document.getElementById('fileInfo').style.display = 'none';
document.getElementById('progressArea').style.display = 'block';
document.getElementById('errorArea').style.display = 'none';
document.getElementById('resultArea').style.display = 'none';
try {
const response = await fetch('/vendor-stockout/api/vendor/import/excel', {
method: 'POST',
headers: {
'X-CSRFToken': vendorCsrfToken
},
body: formData
});
const result = await response.json();
document.getElementById('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('updatedCount').textContent = result.data.updated_count;
document.getElementById('emailCount').textContent = result.data.email_count;
document.getElementById('failedCount').textContent = result.data.failed_count;
document.getElementById('resultArea').style.display = 'block';
} else {
document.getElementById('errorMessage').textContent = result.message;
document.getElementById('errorArea').style.display = 'block';
}
} catch (error) {
document.getElementById('progressArea').style.display = 'none';
document.getElementById('errorMessage').textContent = '網路錯誤: ' + error.message;
document.getElementById('errorArea').style.display = 'block';
}
}
function resetImport() {
selectedFile = null;
document.getElementById('fileInput').value = '';
document.getElementById('fileInfo').style.display = 'none';
document.getElementById('resultArea').style.display = 'none';
document.getElementById('errorArea').style.display = 'none';
}
function refreshVendorList() {
const listTab = new bootstrap.Tab(document.getElementById('list-tab'));
listTab.show();
loadVendorList();
}
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>
{% endblock %}