上傳檔案到「templates」
This commit is contained in:
289
templates/index.html
Normal file
289
templates/index.html
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-TW">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>公司內部訂餐系統</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
.card-img-top {
|
||||||
|
height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-top-left-radius: 15px;
|
||||||
|
border-top-right-radius: 15px;
|
||||||
|
}
|
||||||
|
.category-header {
|
||||||
|
background: rgba(255,255,255,0.95);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.price {
|
||||||
|
color: #28a745;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
.btn-order {
|
||||||
|
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
padding: 10px 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.btn-order:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 5px 15px rgba(255,107,107,0.4);
|
||||||
|
}
|
||||||
|
.hero-section {
|
||||||
|
background: rgba(255,255,255,0.95);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="#">
|
||||||
|
<i class="fas fa-utensils me-2"></i>
|
||||||
|
公司內部訂餐系統
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="hero-section">
|
||||||
|
<h1 class="display-4 fw-bold text-primary mb-3">
|
||||||
|
<i class="fas fa-concierge-bell me-2"></i>
|
||||||
|
今日菜單
|
||||||
|
</h1>
|
||||||
|
<p class="lead text-muted">選擇您喜愛的餐點,享受美味時光</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if menu_items %}
|
||||||
|
{% set categories = menu_items|map(attribute='category')|unique|list %}
|
||||||
|
|
||||||
|
{% for category in categories %}
|
||||||
|
<div class="category-header">
|
||||||
|
<h2 class="h3 mb-0">
|
||||||
|
<i class="fas fa-folder me-2"></i>
|
||||||
|
{{ category }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-5">
|
||||||
|
{% for item in menu_items if item.category == category %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="card h-100">
|
||||||
|
<img src="{{ item.image_url }}" class="card-img-top" alt="{{ item.name }}"
|
||||||
|
onerror="this.src='https://via.placeholder.com/300x200/667eea/ffffff?text=餐點圖片'">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title fw-bold">{{ item.name }}</h5>
|
||||||
|
<p class="card-text text-muted">{{ item.description }}</p>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<span class="price">${{ "%0.2f"|format(item.price) }}</span>
|
||||||
|
<button class="btn btn-order" onclick="addToCart({{ item.id }}, '{{ item.name }}', {{ item.price }})">
|
||||||
|
<i class="fas fa-cart-plus me-2"></i>
|
||||||
|
加入訂單
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-utensils fa-5x text-light mb-3"></i>
|
||||||
|
<h3 class="text-light">暫無菜單資料</h3>
|
||||||
|
<p class="text-light">請聯繫管理員更新菜單</p>
|
||||||
|
<a href="/init" class="btn btn-light btn-lg">
|
||||||
|
<i class="fas fa-sync me-2"></i>
|
||||||
|
初始化菜單
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Shopping Cart Modal -->
|
||||||
|
<div class="modal fade" id="cartModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="fas fa-shopping-cart me-2"></i>
|
||||||
|
我的訂單
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="cartItems">
|
||||||
|
<p class="text-muted text-center">購物車是空的</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||||
|
<h5>總金額: <span id="totalAmount">$0.00</span></h5>
|
||||||
|
<button class="btn btn-success" onclick="submitOrder()">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>
|
||||||
|
提交訂單
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Floating Cart Button -->
|
||||||
|
<div class="position-fixed bottom-0 end-0 m-4">
|
||||||
|
<button class="btn btn-primary btn-lg rounded-pill shadow" data-bs-toggle="modal" data-bs-target="#cartModal">
|
||||||
|
<i class="fas fa-shopping-cart me-2"></i>
|
||||||
|
<span class="badge bg-danger" id="cartCount">0</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
let cart = [];
|
||||||
|
|
||||||
|
function addToCart(id, name, price) {
|
||||||
|
const existingItem = cart.find(item => item.id === id);
|
||||||
|
|
||||||
|
if (existingItem) {
|
||||||
|
existingItem.quantity += 1;
|
||||||
|
} else {
|
||||||
|
cart.push({
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
price: price,
|
||||||
|
quantity: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCartDisplay();
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showToast('已加入購物車: ' + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCartDisplay() {
|
||||||
|
const cartCount = document.getElementById('cartCount');
|
||||||
|
const cartItems = document.getElementById('cartItems');
|
||||||
|
const totalAmount = document.getElementById('totalAmount');
|
||||||
|
|
||||||
|
// Update cart count
|
||||||
|
cartCount.textContent = cart.reduce((total, item) => total + item.quantity, 0);
|
||||||
|
|
||||||
|
// Update cart items
|
||||||
|
if (cart.length === 0) {
|
||||||
|
cartItems.innerHTML = '<p class="text-muted text-center">購物車是空的</p>';
|
||||||
|
} else {
|
||||||
|
cartItems.innerHTML = cart.map(item => `
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-body py-2">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">${item.name}</h6>
|
||||||
|
<small class="text-muted">$${item.price.toFixed(2)} x ${item.quantity}</small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="fw-bold me-3">$${(item.price * item.quantity).toFixed(2)}</span>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" onclick="removeFromCart(${item.id})">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total amount
|
||||||
|
const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
||||||
|
totalAmount.textContent = '$' + total.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromCart(id) {
|
||||||
|
cart = cart.filter(item => item.id !== id);
|
||||||
|
updateCartDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitOrder() {
|
||||||
|
if (cart.length === 0) {
|
||||||
|
showToast('請先選擇餐點', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here you would typically send the order to the server
|
||||||
|
const orderData = {
|
||||||
|
items: cart,
|
||||||
|
total: cart.reduce((sum, item) => sum + (item.price * item.quantity), 0),
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Order submitted:', orderData);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showToast('訂單提交成功!', 'success');
|
||||||
|
|
||||||
|
// Clear cart
|
||||||
|
cart = [];
|
||||||
|
updateCartDisplay();
|
||||||
|
|
||||||
|
// Close modal
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('cartModal')).hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(message, type = 'success') {
|
||||||
|
// Create toast element
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = `position-fixed bottom-0 end-0 m-3 toast align-items-center text-white bg-${type} border-0`;
|
||||||
|
toast.setAttribute('role', 'alert');
|
||||||
|
toast.innerHTML = `
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="toast-body">
|
||||||
|
${message}
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// Initialize and show toast
|
||||||
|
const bsToast = new bootstrap.Toast(toast);
|
||||||
|
bsToast.show();
|
||||||
|
|
||||||
|
// Remove toast after it's hidden
|
||||||
|
toast.addEventListener('hidden.bs.toast', () => {
|
||||||
|
document.body.removeChild(toast);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize cart display
|
||||||
|
updateCartDisplay();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
templates/maintenance.html
Normal file
BIN
templates/maintenance.html
Normal file
Binary file not shown.
550
templates/menu_form.html
Normal file
550
templates/menu_form.html
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-TW">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Menu Items 表單維護系統</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
padding: 10px 25px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: linear-gradient(45deg, #5a6fd8, #6a4190);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.btn-outline-primary {
|
||||||
|
border-color: #667eea;
|
||||||
|
color: #667eea;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
.btn-outline-primary:hover {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.form-control, .form-select {
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
padding: 12px 15px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||||
|
}
|
||||||
|
.table th {
|
||||||
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.active-status {
|
||||||
|
background: linear-gradient(45deg, #48bb78, #38a169);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.inactive-status {
|
||||||
|
background: linear-gradient(45deg, #f56565, #e53e3e);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-bottom: 3px solid #667eea;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.action-buttons .btn {
|
||||||
|
margin: 0 5px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- 導航欄 -->
|
||||||
|
<nav class="navbar navbar-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">
|
||||||
|
<i class="fas fa-utensils me-2"></i>
|
||||||
|
Menu Items 表單維護系統
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- 表單操作區 -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="section-title">
|
||||||
|
<i class="fas fa-pen-to-square me-2"></i>
|
||||||
|
菜單資料表單
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<form id="menuForm" class="row g-3">
|
||||||
|
<input type="hidden" id="formId" name="id">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">
|
||||||
|
<i class="fas fa-utensil-spoon me-2"></i>
|
||||||
|
主菜單名稱 *
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="mainCourse" name="main_course"
|
||||||
|
placeholder="請輸入主菜單名稱" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">
|
||||||
|
<i class="fas fa-carrot me-2"></i>
|
||||||
|
副菜單名稱
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="sideDish" name="side_dish"
|
||||||
|
placeholder="請輸入副菜單名稱(選填)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">
|
||||||
|
<i class="fas fa-ice-cream me-2"></i>
|
||||||
|
甜點/湯品
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="addon" name="addon"
|
||||||
|
placeholder="請輸入甜點或湯品名稱(選填)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">
|
||||||
|
<i class="fas fa-calendar-alt me-2"></i>
|
||||||
|
可訂餐日期 *
|
||||||
|
</label>
|
||||||
|
<input type="date" class="form-control" id="orderDate" name="Order_Date" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">
|
||||||
|
<i class="fas fa-toggle-on me-2"></i>
|
||||||
|
狀態
|
||||||
|
</label>
|
||||||
|
<select class="form-select" id="isActive" name="is_active">
|
||||||
|
<option value="true">啟用</option>
|
||||||
|
<option value="false">停用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-4">
|
||||||
|
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
|
<button type="button" class="btn btn-outline-secondary me-md-2" onclick="resetForm()">
|
||||||
|
<i class="fas fa-undo me-2"></i>
|
||||||
|
重置表單
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>
|
||||||
|
送出表單
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 資料操作區 -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h4 class="section-title mb-0">
|
||||||
|
<i class="fas fa-list me-2"></i>
|
||||||
|
現有菜單資料
|
||||||
|
</h4>
|
||||||
|
<button class="btn btn-outline-primary" onclick="loadData()">
|
||||||
|
<i class="fas fa-sync-alt me-2"></i>
|
||||||
|
重新載入
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜尋篩選 -->
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<input type="text" class="form-control" id="searchMainCourse"
|
||||||
|
placeholder="搜尋主菜單...">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<input type="text" class="form-control" id="searchSideDish"
|
||||||
|
placeholder="搜尋副菜單...">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<select class="form-select" id="searchStatus">
|
||||||
|
<option value="">全部狀態</option>
|
||||||
|
<option value="true">啟用</option>
|
||||||
|
<option value="false">停用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<button class="btn btn-primary w-100" onclick="searchItems()">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 資料表格 -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>主菜單</th>
|
||||||
|
<th>副菜單</th>
|
||||||
|
<th>甜點/湯品</th>
|
||||||
|
<th>訂餐日期</th>
|
||||||
|
<th>狀態</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dataTable">
|
||||||
|
<!-- 資料將透過JavaScript動態載入 -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="loading" class="loading">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">載入中...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">資料載入中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast通知 -->
|
||||||
|
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="me-auto">
|
||||||
|
<i class="fas fa-bell me-2"></i>
|
||||||
|
系統訊息
|
||||||
|
</strong>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body" id="toastMessage"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const API_BASE_URL = 'http://127.0.0.1:5000/v1/menu_items';
|
||||||
|
let toast = null;
|
||||||
|
|
||||||
|
// 初始化頁面
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
toast = new bootstrap.Toast(document.querySelector('.toast'));
|
||||||
|
loadData();
|
||||||
|
|
||||||
|
// 表單提交事件
|
||||||
|
document.getElementById('menuForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitForm();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 設定今天為預設日期
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
document.getElementById('orderDate').value = today;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 顯示通知
|
||||||
|
function showToast(message, type = 'info') {
|
||||||
|
const toastElement = document.querySelector('.toast');
|
||||||
|
toastElement.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info');
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case 'success':
|
||||||
|
toastElement.classList.add('bg-success', 'text-white');
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
toastElement.classList.add('bg-danger', 'text-white');
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
toastElement.classList.add('bg-warning', 'text-dark');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
toastElement.classList.add('bg-info', 'text-white');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('toastMessage').textContent = message;
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 載入資料
|
||||||
|
async function loadData() {
|
||||||
|
showLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_BASE_URL);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.status === 'success') {
|
||||||
|
renderTable(result.data);
|
||||||
|
showToast('資料載入成功', 'success');
|
||||||
|
} else {
|
||||||
|
showToast('載入資料失敗: ' + result.message, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast('網路錯誤: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜尋資料
|
||||||
|
async function searchItems() {
|
||||||
|
showLoading(true);
|
||||||
|
const mainCourse = document.getElementById('searchMainCourse').value;
|
||||||
|
const sideDish = document.getElementById('searchSideDish').value;
|
||||||
|
const status = document.getElementById('searchStatus').value;
|
||||||
|
|
||||||
|
let url = API_BASE_URL + '?';
|
||||||
|
if (mainCourse) url += `main_course=${encodeURIComponent(mainCourse)}&`;
|
||||||
|
if (sideDish) url += `side_dish=${encodeURIComponent(sideDish)}&`;
|
||||||
|
if (status) url += `is_active=${status}&`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.status === 'success') {
|
||||||
|
renderTable(result.data);
|
||||||
|
showToast(`找到 ${result.data.length} 筆資料`, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('搜尋失敗: ' + result.message, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast('搜尋錯誤: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染表格
|
||||||
|
function renderTable(data) {
|
||||||
|
const tbody = document.getElementById('dataTable');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center text-muted py-5">
|
||||||
|
<i class="fas fa-inbox fa-3x mb-3"></i>
|
||||||
|
<br>
|
||||||
|
暫無資料,請新增菜單項目
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
const row = `
|
||||||
|
<tr>
|
||||||
|
<td class="fw-bold">${item.Id}</td>
|
||||||
|
<td>${item.main_course || '-'}</td>
|
||||||
|
<td>${item.side_dish || '-'}</td>
|
||||||
|
<td>${item.addon || '-'}</td>
|
||||||
|
<td>${item.Order_Date || '-'}</td>
|
||||||
|
<td>
|
||||||
|
<span class="status-badge ${item.is_active ? 'active-status' : 'inactive-status'}">
|
||||||
|
<i class="fas ${item.is_active ? 'fa-check-circle' : 'fa-times-circle'} me-1"></i>
|
||||||
|
${item.is_active ? '啟用' : '停用'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn btn-sm btn-warning" onclick="editItem(${item.Id})"
|
||||||
|
title="編輯">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="deleteItem(${item.Id})"
|
||||||
|
title="刪除">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
tbody.innerHTML += row;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顯示/隱藏載入動畫
|
||||||
|
function showLoading(show) {
|
||||||
|
document.getElementById('loading').style.display = show ? 'block' : 'none';
|
||||||
|
document.querySelector('.table-responsive').style.display = show ? 'none' : 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表單
|
||||||
|
function resetForm() {
|
||||||
|
document.getElementById('menuForm').reset();
|
||||||
|
document.getElementById('formId').value = '';
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
document.getElementById('orderDate').value = today;
|
||||||
|
showToast('表單已重置', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表單
|
||||||
|
async function submitForm() {
|
||||||
|
const formData = new FormData(document.getElementById('menuForm'));
|
||||||
|
const data = Object.fromEntries(formData.entries());
|
||||||
|
const id = data.id;
|
||||||
|
|
||||||
|
// 轉換布林值
|
||||||
|
data.is_active = data.is_active === 'true';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response, result;
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
// 更新現有資料
|
||||||
|
delete data.id;
|
||||||
|
response = await fetch(`${API_BASE_URL}/${id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
result = await response.json();
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
showToast('資料更新成功', 'success');
|
||||||
|
resetForm();
|
||||||
|
loadData();
|
||||||
|
} else {
|
||||||
|
showToast('更新失敗: ' + result.message, 'error');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 新增資料
|
||||||
|
response = await fetch(API_BASE_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
result = await response.json();
|
||||||
|
|
||||||
|
if (response.status === 201) {
|
||||||
|
showToast('資料新增成功', 'success');
|
||||||
|
resetForm();
|
||||||
|
loadData();
|
||||||
|
} else {
|
||||||
|
showToast('新增失敗: ' + result.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast('操作錯誤: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 編輯資料
|
||||||
|
async function editItem(id) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/${id}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.status === 'success') {
|
||||||
|
const item = result.data;
|
||||||
|
|
||||||
|
// 填充表單
|
||||||
|
document.getElementById('formId').value = item.Id;
|
||||||
|
document.getElementById('mainCourse').value = item.main_course || '';
|
||||||
|
document.getElementById('sideDish').value = item.side_dish || '';
|
||||||
|
document.getElementById('addon').value = item.addon || '';
|
||||||
|
document.getElementById('orderDate').value = item.Order_Date || '';
|
||||||
|
document.getElementById('isActive').value = item.is_active ? 'true' : 'false';
|
||||||
|
|
||||||
|
// 滾動到表單
|
||||||
|
document.getElementById('menuForm').scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
});
|
||||||
|
|
||||||
|
showToast('已載入編輯資料', 'info');
|
||||||
|
} else {
|
||||||
|
showToast('載入資料失敗: ' + result.message, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast('載入錯誤: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刪除資料
|
||||||
|
async function deleteItem(id) {
|
||||||
|
if (!confirm('確定要刪除這筆菜單資料嗎?此操作無法復原。')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 204) {
|
||||||
|
showToast('資料刪除成功', 'success');
|
||||||
|
loadData();
|
||||||
|
} else {
|
||||||
|
const result = await response.json();
|
||||||
|
showToast('刪除失敗: ' + result.message, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast('刪除錯誤: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in New Issue
Block a user