This commit is contained in:
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.145"
|
||||
SYSTEM_VERSION = "V10.146"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ notification_bp = Blueprint('notification', __name__)
|
||||
def notification_templates_page():
|
||||
"""通知模板管理頁面"""
|
||||
return render_template('notification_templates.html',
|
||||
system_version=SYSTEM_VERSION)
|
||||
system_version=SYSTEM_VERSION,
|
||||
active_page='notification_templates')
|
||||
|
||||
|
||||
# ==========================================
|
||||
|
||||
@@ -229,7 +229,11 @@ def settings():
|
||||
@system_public_bp.route('/system_settings')
|
||||
def system_settings_page():
|
||||
"""系統設定與匯入頁面"""
|
||||
return render_template('system_settings.html', system_version=SYSTEM_VERSION)
|
||||
return render_template(
|
||||
'system_settings.html',
|
||||
system_version=SYSTEM_VERSION,
|
||||
active_page='system_settings',
|
||||
)
|
||||
|
||||
|
||||
@system_public_bp.route('/ai_automation_smoke')
|
||||
|
||||
@@ -26,7 +26,7 @@ user_bp = Blueprint('user_bp', __name__)
|
||||
@admin_required
|
||||
def user_management():
|
||||
"""用戶管理頁面(僅管理員)"""
|
||||
return render_template('user_management.html')
|
||||
return render_template('user_management.html', active_page='user_management')
|
||||
|
||||
|
||||
@user_bp.route('/change_password')
|
||||
@@ -34,14 +34,18 @@ def user_management():
|
||||
def change_password():
|
||||
"""修改密碼頁面"""
|
||||
password_requirements = get_password_requirements()
|
||||
return render_template('change_password.html', password_requirements=password_requirements)
|
||||
return render_template(
|
||||
'change_password.html',
|
||||
password_requirements=password_requirements,
|
||||
active_page='change_password',
|
||||
)
|
||||
|
||||
|
||||
@user_bp.route('/login_history')
|
||||
@admin_required
|
||||
def login_history_page():
|
||||
"""登入歷史頁面(僅管理員)"""
|
||||
return render_template('login_history.html')
|
||||
return render_template('login_history.html', active_page='login_history')
|
||||
|
||||
|
||||
# ==========================================
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}修改密碼 - WOOO TECH{% endblock %}
|
||||
{% block title %}修改密碼 - EwoooC{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
{# 群組 5: 系統(terra) #}
|
||||
<div class="momo-nav-group">
|
||||
<div class="momo-nav-group-title">系統</div>
|
||||
<a class="momo-nav-link {% if _active_page in ['settings', 'system_settings', 'logs', 'crawler', 'user_management', 'ai_automation_smoke'] %}is-active{% endif %}" href="/settings">
|
||||
<a class="momo-nav-link {% if _active_page in ['settings', 'system_settings', 'logs', 'crawler', 'user_management', 'login_history', 'change_password', 'notification_templates', 'ai_automation_smoke'] %}is-active{% endif %}" href="/settings">
|
||||
<span class="momo-nav-icon"><i class="fas fa-gear"></i></span>
|
||||
<span class="momo-nav-label">系統管理</span>
|
||||
<span class="momo-nav-code">14</span>
|
||||
|
||||
@@ -350,7 +350,8 @@
|
||||
'obs_promotion_review', 'obs_rag_queries', 'obs_quality_trend',
|
||||
'obs_ppt_audit'] %}
|
||||
{% set _group_system = ['settings', 'system_settings', 'logs', 'crawler',
|
||||
'user_management', 'ai_automation_smoke'] %}
|
||||
'user_management', 'login_history', 'change_password',
|
||||
'notification_templates', 'ai_automation_smoke'] %}
|
||||
{% if _page in _group_monitor %}{% set _page_group = 'monitor' %}
|
||||
{% elif _page in _group_analytics %}{% set _page_group = 'analytics' %}
|
||||
{% elif _page in _group_ops %}{% set _page_group = 'ops' %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}登入歷史 - WOOO TECH{% endblock %}
|
||||
{% block title %}登入歷史 - EwoooC{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}通知模板管理{% endblock %}
|
||||
|
||||
|
||||
@@ -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(135deg, #f5f7fa 0%, #e8ecf1 100%);
|
||||
min-height: 100vh;
|
||||
padding-top: 70px;
|
||||
}
|
||||
{% block title %}系統設定與資料匯入 - EwoooC{% endblock %}
|
||||
|
||||
/* Custom Dark Navbar - 統一藍色漸層 */
|
||||
.navbar.bg-custom-dark {
|
||||
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%) !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.system-import-page {
|
||||
display: grid;
|
||||
gap: var(--momo-space-4, 16px);
|
||||
}
|
||||
|
||||
.navbar.bg-custom-dark .navbar-brand {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.system-import-page .system-import-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--momo-space-4, 16px);
|
||||
padding: var(--momo-space-4, 16px) var(--momo-space-5, 24px);
|
||||
background: var(--momo-bg-surface);
|
||||
border: 1px 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.3s;
|
||||
}
|
||||
.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-custom-dark .navbar-nav .nav-link:hover {
|
||||
color: #ffffff;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.system-import-page .system-import-head p {
|
||||
margin: var(--momo-space-1, 4px) 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: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.system-import-page .table-container {
|
||||
padding: var(--momo-space-5, 24px);
|
||||
background: var(--momo-bg-surface);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: var(--momo-radius-md);
|
||||
}
|
||||
|
||||
.navbar.bg-custom-dark .navbar-text {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
.system-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 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.system-import-page .import-panel {
|
||||
padding: var(--momo-space-4, 16px);
|
||||
background: var(--momo-bg-paper);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: var(--momo-radius-md);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: white;
|
||||
border-radius: .5rem;
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, .125), 0 1px 3px rgba(0, 0, 0, .2);
|
||||
padding: 1.25rem;
|
||||
}
|
||||
</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, 16px);
|
||||
}
|
||||
}
|
||||
</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-100" 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這將會打包所有程式碼與資料庫檔案。')) {
|
||||
fetch('/api/backup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
function getCSRFToken() {
|
||||
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('excelFile');
|
||||
const file = fileInput.files[0];
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
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="uploadExcel()"]');
|
||||
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 %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}用戶管理 - WOOO TECH{% endblock %}
|
||||
{% block title %}用戶管理 - EwoooC{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user