@@ -1,342 +1,339 @@
<!DOCTYPE html>
< html lang = "zh-TW" >
{% extends "ewoooc_base.html" %}
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< meta name = "csrf-token" content = "{{ csrf_token() }}" >
< title > 系統設定/匯入 - WOOO TECH< / 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 : - apple-system , BlinkMacSystemFont , "Segoe UI" , Roboto , "Helvetica Neue" , Arial , sans-serif ;
background : linear-gradient ( 135 deg , #f5f7fa 0 % , #e8ecf1 100 % ) ;
min-height : 100 vh ;
padding-top : 70 px ;
}
{% block title %}系統設定與資料匯入 - EwoooC{% endblock %}
/* Custom Dark Navbar - 統一藍色漸層 */
. navbar . bg-custom-dark {
background : linear-gradient ( 135 deg , #1e3c72 0 % , #2a5298 100 % ) !important ;
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.15 ) ;
border-bottom : 1 px solid rgba ( 255 , 255 , 255 , 0.1 ) ;
}
{% block extra_css %}
< style >
. system-import-page {
display : grid ;
gap : var ( - - momo - space -4 , 16 px ) ;
}
. navbar . bg-custom-dark . navbar-bran d {
color : #ffffff ;
font-weight : 600 ;
}
. system-import-page . system-import-hea d {
display : flex ;
align-items : flex-start ;
justify-content : space-between ;
gap : var ( - - momo - space -4 , 16 px ) ;
padding : var ( - - momo - space -4 , 16 px ) var ( - - momo - space -5 , 24 px ) ;
background : var ( - - momo - bg - surface ) ;
border : 1 px solid var ( - - momo - border - light ) ;
border-radius : var ( - - momo - radius - md ) ;
}
. navbar . bg-custom-dark . navbar-nav . nav-link {
color : rgba ( 255 , 255 , 255 , 0.85 ) ;
font-weight : 500 ;
transition : all 0.3 s ;
}
. system-import-page . system-import-head h1 {
margin : 0 ;
color : var ( - - momo - text - primary ) ;
font-family : var ( - - momo - font - display ) ;
font-size : var ( - - momo - text - headline ) ;
font-weight : 700 ;
line-height : var ( - - momo - line - height - tight ) ;
letter-spacing : 0 ;
}
. navbar . bg-cu sto m-dark . navbar-nav . nav-link : hover {
color : #ffffff ;
background : rgba ( 255 , 255 , 255 , 0.1 ) ;
border-radius : 6 px ;
}
. sy ste m-import-page . system-import-head p {
margin : var ( - - momo - space -1 , 4 px ) 0 0 ;
color : var ( - - momo - text - secondary ) ;
font-size : var ( - - momo - text - body - sm ) ;
}
. navbar . bg-custom-dark . navbar-nav . nav-link . active {
color : #ffffff ;
background : rgba ( 255 , 255 , 255 , 0.15 ) ;
border-radius : 6 px ;
font-weight : 600 ;
}
. system-import-page . table-container {
padding : var ( - - momo - space -5 , 24 px ) ;
background : var ( - - momo - bg - surface ) ;
border : 1 px solid var ( - - momo - border - light ) ;
border-radius : var ( - - momo - radius - md ) ;
}
. navbar . bg-cu sto m-dark . navbar-text {
color : rgba ( 255 , 255 , 255 , 0.75 ) ;
}
. sy ste m-import-page . table-container h5 {
color : var ( - - momo - text - primary ) ;
font-family : var ( - - momo - font - display ) ;
font-size : var ( - - momo - text - title ) ;
font-weight : 700 ;
}
. navbar {
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.05 ) ;
}
. system-import-page . import-panel {
padding : var ( - - momo - space -4 , 16 px ) ;
background : var ( - - momo - bg - paper ) ;
border : 1 px solid var ( - - momo - border - light ) ;
border-radius : var ( - - momo - radius - md ) ;
}
. table-container {
background : white ;
border-radius : .5 rem ;
box-shadow : 0 0 1 px rgba ( 0 , 0 , 0 , .125 ) , 0 1 px 3 px rgba ( 0 , 0 , 0 , .2 ) ;
padding : 1.25 rem ;
}
< / style >
< / head >
@ media ( max-width : 760px ) {
. system-import-page . system-import-head ,
. system-import-page . table-container . d-flex {
flex-direction : column ;
}
< body >
{% include 'components/_navbar.html' %}
. system-import-page . table-container {
padding : var ( - - momo - space -4 , 16 px ) ;
}
}
< / style >
{% endblock %}
< div class = "container" >
< div class = "d-flex justify-content-between align-items-center mb-4 mt-4 " >
< h4 class = "mb-0" > < i class = "fas fa-cogs me-2" > < / i > 系統設定與資料匯入< / h4 >
{% block content %}
< div class = "system-import-page " >
< header class = "system-import-head" >
< div >
< h1 > < i class = "fas fa-cogs me-2" > < / i > 系統設定與資料匯入< / h1 >
< p > 集中管理備份、月總表、即時業績與一般 Excel 匯入。< / p >
< / div >
< span class = "badge bg-primary" > 版本 {{ system_version }}< / span >
< / header >
< div class = "table-container mb-4 " >
< h5 class = "mb-3" > 系統維護< / h5 >
< div class = "d-flex align-items-center justify-content-between" >
< div >
< h6 class = "fw-bold mb-1" > 完整備份< / h6 >
< p class = "text-muted mb-0 small" > 將目前的系統程式碼與資料庫打包存檔 (目前版本: {{ system_version }}) 。< / p >
< / div >
< button class = "btn btn-outline-secondary" onclick = "triggerBackup()" >
< i class = "fas fa-file-archive me-2" > < / i > 建立備份
< / button >
< div class = "table-container" >
< h5 class = "mb-3" > 系統維護< / h5 >
< div class = "d-flex align-items-center justify-content-between gap-3 " >
< div >
< h6 class = "fw-bold mb-1" > 完整備份< / h6 >
< p class = "text-muted mb-0 small" > 將目前的系統程式碼與資料庫打包存檔。< / p >
< / div >
< button class = "btn btn-outline-secondary" onclick = "triggerBackup()" >
< i class = "fas fa-file-archive me-2" > < / i > 建立備份
< / button >
< / div >
< / div >
<!-- ================= NEW: Monthly Summary Analysis Import ================= -- >
< div class = "table-container mb-4" >
< h5 class = "mb-3 text-primary" > < i class = "fas fa-file-import me-2" > < / i > 月份總表數據分析匯入< / h5 >
< div class = "p-4 border rounded bg-light border-primary border-opacity-25 " >
< div class = "row align-items-center " >
< div class = "col-md-9" >
< h6 class = "fw-bold mb-1 " > 月份業績匯總分析 (42 欄位版) < / h6 >
< p class = "text-muted small mb-3" > 此功能專用於匯入「月份總表數據分析」Excel。系統會自動根據「年」與「月」欄位, < strong
class = "text-danger" > 覆蓋更新< / strong > 該月份之歷史數據,適合大批量資料 (如 8 萬筆+)。< / p >
< input class = "form-control" type = "file" id = "monthlySummaryFile" accept = ".xlsx, .xls" >
< / div >
< div class = "col-md-3 text-end" >
< button class = "btn btn-primary w-100 py-2 mt-3 mt-md-0 shadow-sm"
onclick = "uploadMonthlySummary()" >
< i class = "fas fa-upload me-2" > < / i > 執行大批量匯入
< / button >
< / div >
< div class = "table-container" >
< h5 class = "mb-3 text-primary" > < i class = "fas fa-file-import me-2" > < / i > 月份總表數據分析匯入< / h5 >
< div class = "import-panel" >
< div class = "row align-items-center g-3 " >
< div class = "col-md-9 " >
< h6 class = "fw-bold mb-1" > 月份業績匯總分析 (42 欄位版)< / h6 >
< p class = "text-muted small mb-3 " > 此功能專用於匯入「月份總表數據分析」Excel。系統會自動根據「年」與「月」欄位, < strong class = "text-danger" > 覆蓋更新< / strong > 該月份之歷史數據,適合大批量資料。 < / p >
< input class = "form-control" type = "file" id = "monthlySummaryFile" accept = ".xlsx, .xls" >
< / div >
< div id = "monthlyImportProgress" class = "mt-3 d-none ">
< div class = "progress" style = "height: 10px; " >
< div class = "progress-bar progress-bar-striped progress-bar-animated" role = "progressbar"
style = "width: 100%" > < / div >
< / div >
< p class = "text-center small text-primary mt-2 mb-0" > 資料處理中,請稍候...< / p >
< div class = "col-md-3 text-end ">
< button class = "btn btn-primary w-100 py-2 mt-3 mt-md-0" onclick = "uploadMonthlySummary() " >
< i class = "fas fa-upload me-2" > < / i > 執行大批量匯入
< / button >
< / div >
< / div >
< / div >
<!-- ================= OLD: Sales Report Import ================= -- >
< div class = "table-container mb-4" >
< h5 class = "mb-3" > < i class = "fas fa-file-invoice-dollar me-2" > < / i > 即時業績(全月)匯入< / h5 >
< div class = "p-4 border rounded bg-white" >
< div class = "row align-items-center" >
< div class = "col-md-9" >
< h6 class = "fw-bold mb-1" > 匯入即時業績 (全月版)< / h6 >
< p class = "text-muted small mb-3" > 請上傳檔名包含「即時業績」且為「全月」版本之 Excel。資料將自動匯入至專屬分析明細表。< / p >
< input class = "form-control" type = "file" id = "salesReportFile" accept = ".xlsx, .xls" >
< / div >
< div class = "col-md-3 text-end" >
< button class = "btn btn-outline-primary w-100 py-2 mt-3 mt-md-0" onclick = "uploadSalesReport()" >
< i class = "fas fa-upload me-2" > < / i > 執行業績匯入
< / button >
< / div >
< div id = "monthlyImportProgress" class = "mt-3 d-none" >
< div class = "progress" style = "height: 10px;" >
< div class = "progress-bar progress-bar-striped progress-bar-animated" role = "progressbar" style = "width: 100%" > < / div >
< / div >
< p class = "text-center small text-primary mt-2 mb-0" > 資料處理中,請稍候...< / p >
< / div >
< / div >
<!-- ================= CLARIFIED: Generic Import ================= -- >
< div class = "table-container mb-4" >
< h5 class = "mb-3" > 一般 Excel 匯入 (自動建表)< / h5 >
< div class = "row align-items-end" >
< div class = "col-md-8 " >
< label for = "excelFile" class = "form-label text-muted small" > 選擇任何 Excel 檔 - 系統將依據檔名自動建立新資料表
(若表名重複將會覆蓋)< / label >
< input class = "form-control" type = "file" id = "excelFile" accept = ".xlsx, .xls" >
< / div >
< div class = "table-container" >
< h5 class = "mb-3" > < i class = "fas fa-file-invoice-dollar me-2" > < / i > 即時業績(全月)匯入< / h5 >
< div class = "import-panel " >
< div class = "row align-items-center g-3" >
< div class = "col-md-9" >
< h6 class = "fw-bold mb-1" > 匯入即時業績 (全月版)< / h6 >
< p class = "text-muted small mb-3" > 請上傳檔名包含「即時業績」且為「全月」版本之 Excel。資料將自動匯入至專屬分析明細表。< / p >
< input class = "form-control" type = "file" id = "salesReportFile" accept = ".xlsx, .xls" >
< / div >
< div class = "col-md-4 mt-3 mt-md-0 " >
< button class = "btn btn-success w-10 0" onclick = "uploadExcel ()" >
< i class = "fas fa-table me-2" > < / i > 匯入並建立通用資料表
< div class = "col-md-3 text-end " >
< button class = "btn btn-outline-primary w-100 py-2 mt-3 mt-md- 0" onclick = "uploadSalesReport ()" >
< i class = "fas fa-upload me-2" > < / i > 執行業績 匯入
< / button >
< / div >
< / div >
< / div >
< / div >
< script src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" > < / script >
< script >
// Helper function to get CSRF token
function getCSRFToken ( ) {
return document . querySelector ( 'meta[name="csrf-token"]' ) . getAttribute ( 'content' ) ;
}
< div class = "table-container" >
< h5 class = "mb-3" > 一般 Excel 匯入 (自動建表)< / h5 >
< div class = "row align-items-end g-3" >
< div class = "col-md-8" >
< label for = "excelFile" class = "form-label text-muted small" > 選擇任何 Excel 檔,系統將依據檔名自動建立新資料表;若表名重複將會覆蓋。< / label >
< input class = "form-control" type = "file" id = "excelFile" accept = ".xlsx, .xls" >
< / div >
< div class = "col-md-4" >
< button class = "btn btn-success w-100" onclick = "uploadExcel()" >
< i class = "fas fa-table me-2" > < / i > 匯入並建立通用資料表
< / button >
< / div >
< / div >
< / div >
< / div >
{% endblock %}
function triggerBackup ( ) {
if ( confirm ( '確定要執行系統完整備份嗎?\n這將會打包所有程式碼與資料庫檔案。' ) ) {
f etch ( '/api/backup' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
{% block extra_js %}
< script >
function g etCSRFToken ( ) {
return document . querySelector ( 'meta[name="csrf-token"]' ) . getAttribute ( 'content' ) ;
}
function triggerBackup ( ) {
if ( confirm ( '確定要執行系統完整備份嗎?\n這將會打包所有程式碼與資料庫檔案。' ) ) {
fetch ( '/api/backup' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
}
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success' ) {
alert ( data . message ) ;
if ( data . download _url ) {
window . location . href = data . download _url ;
}
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success' ) {
alert ( data . message ) ;
if ( data . download _url ) {
window . location . href = data . download _url ; // 自動觸發下載
}
} else {
alert ( '錯誤: ' + data . message ) ;
}
} )
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '備份請求失敗,請檢查日誌。' ) ;
} ) ;
}
}
function uploadSalesReport ( ) {
const fileInput = document . getElementById ( 'salesReportFile' ) ;
const file = fileInput . files [ 0 ] ;
if ( ! file ) {
alert ( '請先選擇一個業績報表檔案' ) ;
return ;
}
// 提醒使用者檔名規則
if ( ! file . name . includes ( '即時業績' ) || ! file . name . includes ( '全月' ) ) {
if ( ! confirm ( '檔案名稱似乎不符合「即時業績(全月)」的格式,確定要繼續匯入嗎?\n系統將嘗試根據檔名建立資料表。' ) ) {
return ;
} else {
alert ( '錯誤: ' + data . message ) ;
}
} else if ( ! confirm ( '確定要匯入此份業績報表嗎?\n資料將會累加至 `realtime_sales_monthly` 資料表的現有內容中。' ) ) {
return ;
}
const formData = new FormData ( ) ;
formData . append ( 'file' , file ) ;
const btn = document . querySelector ( 'button[onclick="uploadSalesReport()"]' ) ;
const originalText = btn . innerHTML ;
btn . innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>匯入中...' ;
btn . disabled = true ;
fetch ( '/api/import_excel' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
} ,
body : formData
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success ' ) {
if ( data . table === 'realtime_sales_monthly' ) {
alert ( '業績報表匯入成功!\n資料表: ' + data . table + '\n共 ' + data . rows + ' 筆資料已累加寫入。' ) ;
} else {
alert ( '匯入操作完成。\n注意: 系統偵測到的資料表名稱為「' + data . table + '」,而非預期的 `realtime_sales_monthly`。\n共寫入 ' + data . rows + ' 筆資料。' ) ;
}
fileInput . value = '' ; // 清空輸入
} else {
alert ( '匯入失敗: ' + data . message ) ;
}
} )
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '發生系統錯誤,請檢查日誌' ) ;
} )
. finally ( ( ) => {
btn . innerHTML = originalText ;
btn . disabled = false ;
} ) ;
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '備份請求失敗,請檢查日誌。 ') ;
} ) ;
}
}
function uploadSalesReport ( ) {
const fileInput = document . getElementById ( 'salesReportFile' ) ;
const file = fileInput . files [ 0 ] ;
if ( ! file ) {
alert ( '請先選擇一個業績報表檔案' ) ;
return ;
}
if ( ! file . name . includes ( '即時業績' ) || ! file . name . includes ( '全月' ) ) {
if ( ! confirm ( '檔案名稱似乎不符合「即時業績(全月)」的格式,確定要繼續匯入嗎?\n系統將嘗試根據檔名建立資料表。' ) ) {
return ;
}
} else if ( ! confirm ( '確定要匯入此份業績報表嗎?\n資料將會累加至 `realtime_sales_monthly` 資料表的現有內容中。' ) ) {
return ;
}
function uploadExcel ( ) {
const fileInput = document . getElementById ( 'excelF ile' ) ;
const file = fileInput . files [ 0 ] ;
const formData = new FormData ( ) ;
formData . append ( 'file' , f ile) ;
if ( ! file ) {
alert ( '請先選擇一個 Excel 檔案' ) ;
return ;
const btn = document . querySelector ( 'button[onclick="uploadSalesReport()"]' ) ;
const originalText = btn . innerHTML ;
btn . innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>匯入中...' ;
btn . disabled = true ;
fetch ( '/api/import_excel' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
} ,
body : formData
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success' ) {
if ( data . table === 'realtime_sales_monthly' ) {
alert ( '業績報表匯入成功!\n資料表: ' + data . table + '\n共 ' + data . rows + ' 筆資料已累加寫入。' ) ;
} else {
alert ( '匯入操作完成。\n注意: 系統偵測到的資料表名稱為「' + data . table + '」,而非預期的 `realtime_sales_monthly`。\n共寫入 ' + data . rows + ' 筆資料。' ) ;
}
fileInput . value = '' ;
} else {
alert ( '匯入失敗: ' + data . message ) ;
}
} )
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '發生系統錯誤,請檢查日誌' ) ;
} )
. finally ( ( ) => {
btn . innerHTML = originalText ;
btn . disabled = false ;
} ) ;
}
if ( ! confirm ( '確定要匯入嗎?\n這將會建立一張新的資料表 (若名稱相同則會覆蓋)。' ) ) {
return ;
function uploadExcel ( ) {
const fileInput = document . getElementById ( 'excelFile' ) ;
const file = fileInput . files [ 0 ] ;
if ( ! file ) {
alert ( '請先選擇一個 Excel 檔案' ) ;
return ;
}
if ( ! confirm ( '確定要匯入嗎?\n這將會建立一張新的資料表 (若名稱相同則會覆蓋)。' ) ) {
return ;
}
const formData = new FormData ( ) ;
formData . append ( 'file' , file ) ;
const btn = document . querySelector ( 'button[onclick="uploadExcel()"]' ) ;
const originalText = btn . innerHTML ;
btn . innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>處理中...' ;
btn . disabled = true ;
fetch ( '/api/import_excel' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
} ,
body : formData
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success' ) {
alert ( '匯入成功!\n已建立資料表: ' + data . table + '\n共寫入 ' + data . rows + ' 筆資料。' ) ;
fileInput . value = '' ;
} else {
alert ( '匯入失敗: ' + data . message ) ;
}
} )
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '發生系統錯誤,請檢查日誌' ) ;
} )
. finally ( ( ) => {
btn . innerHTML = originalText ;
btn . disabled = false ;
} ) ;
}
const formData = new FormData ( ) ;
formData . append ( 'file' , file ) ;
function uploadMonthlySummary ( ) {
const fileInput = document . getElementById ( 'monthlySummaryFile' ) ;
const file = fileInput . files [ 0 ] ;
const progress = document . getElementById ( 'monthlyImportProgress' ) ;
// 顯示 loading 狀態
const btn = document . querySelector ( 'button[onclick="upload Excel()"] ' ) ;
const originalText = btn . innerHTML ;
btn . innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>處理中...' ;
btn . disabled = true ;
if ( ! file ) {
alert ( '請先選擇一個月份總表 Excel 檔案 ') ;
return ;
}
fetch ( '/api/import_excel' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
} ,
body : formData
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success' ) {
alert ( '匯入成功!\n已建立資料表: ' + data . table + '\n共寫入 ' + data . rows + ' 筆資料。' ) ;
fileInput . value = '' ; // 清空輸入
} else {
alert ( '匯入失敗: ' + data . message ) ;
}
} )
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '發生系統錯誤,請檢查日誌' ) ;
} )
. finally ( ( ) => {
btn . innerHTML = originalText ;
btn . disabled = false ;
} ) ;
}
if ( ! confirm ( ` 確定要匯入「 ${ file . name } 」嗎? \n 資料庫將根據檔案中的年月份自動覆蓋既有數據。 \n 處理大量資料可能需要較長時間,請勿關閉視窗。 ` ) ) {
return ;
}
function uploadMonthlySummary ( ) {
const fileInput = document . getElementById ( 'monthlySummaryFile' ) ;
const file = fileInput . files [ 0 ] ;
const progress = document . getElementById ( 'monthlyImportProgress' ) ;
const formData = new FormData ( ) ;
formData . append ( 'file' , file ) ;
if ( ! file ) {
alert ( '請先選擇一個月份總表 Excel 檔案' ) ;
return ;
const btn = document . querySelector ( 'button[onclick="uploadMonthlySummary()"]' ) ;
const originalText = btn . innerHTML ;
btn . innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>匯入中...' ;
btn . disabled = true ;
progress . classList . remove ( 'd-none' ) ;
fetch ( '/api/import/monthly_summary' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
} ,
body : formData
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success' ) {
alert ( '匯入完成:' + data . message ) ;
fileInput . value = '' ;
} else {
alert ( '匯入失敗: ' + data . message ) ;
}
if ( ! confirm ( ` 確定要匯入「 ${ file . name } 」嗎? \n 資料庫將根據檔案中的年月份自動覆蓋既有數據。 \n 處理大量資料可能需要較長時間,請勿關閉視窗。 ` ) ) {
return ;
}
const formData = new FormData ( ) ;
formData . append ( 'file' , file ) ;
const btn = document . querySelector ( 'button[onclick="uploadMonthlySummary()"] ') ;
const originalText = btn . innerHTML ;
btn . innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>匯入中...' ;
btn . disabled = true ;
progress . classList . remove ( 'd-none' ) ;
fetch ( '/api/import/monthly_summary' , {
method : 'POST' ,
headers : {
'X-CSRFToken' : getCSRFToken ( )
} ,
body : formData
} )
. then ( response => response . json ( ) )
. then ( data => {
if ( data . status === 'success' ) {
alert ( '🎉 ' + data . message ) ;
fileInput . value = '' ;
} else {
alert ( '❌ 匯入失敗: ' + data . message ) ;
}
} )
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '發生連線錯誤或系統超時,請檢查背景處理狀態。' ) ;
} )
. finally ( ( ) => {
btn . innerHTML = originalText ;
btn . disabled = false ;
progress . classList . add ( 'd-none' ) ;
} ) ;
}
< / script >
< / body >
< / html >
} )
. catch ( error => {
console . error ( 'Error:' , error ) ;
alert ( '發生連線錯誤或系統超時,請檢查背景處理狀態。' ) ;
} )
. finally ( ( ) => {
btn . innerHTML = originalText ;
btn . disabled = false ;
progress . classList . add ( 'd-none ') ;
} ) ;
}
< / script >
{% endblock %}