DeepLearning101 commited on
Commit
7c22e44
·
verified ·
1 Parent(s): 4a4796d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -55
app.py CHANGED
@@ -12,7 +12,9 @@ SUPABASE_URL = os.getenv("SUPABASE_URL")
12
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
13
  GAS_MAIL_URL = os.getenv("GAS_MAIL_URL")
14
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
15
- PUBLIC_SPACE_URL = "https://deeplearning101-ciecietaipei.hf.space" # 您的官網網址
 
 
16
 
17
  REAL_ADMIN_USER = os.getenv("ADMIN_USER") or "Deep Learning 101"
18
  REAL_ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") or "2016-11-11"
@@ -20,7 +22,37 @@ REAL_ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") or "2016-11-11"
20
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
21
 
22
  # ==========================================
23
- # 模組 1訂位與通知管理 (包含 No-Show)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # ==========================================
25
 
26
  def get_bookings():
@@ -34,7 +66,6 @@ def get_bookings():
34
 
35
  def send_confirmation_hybrid(booking_id):
36
  if not booking_id: return "❌ 請輸入訂單 ID"
37
-
38
  try:
39
  res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
40
  if not res.data: return f"❌ 找不到 ID: {booking_id}"
@@ -94,12 +125,16 @@ def send_confirmation_hybrid(booking_id):
94
  return log_msg
95
  except Exception as e: return f"嚴重錯誤: {str(e)}"
96
 
97
- def mark_no_show(booking_id):
98
- """將訂位標記 No-Show 黑名單"""
99
  if not booking_id: return "❌ 請輸入訂單 ID"
 
100
  try:
101
- supabase.table("bookings").update({"status": "No-Show"}).eq("id", booking_id).execute()
102
- return f"🚫 訂單 {booking_id} 已成功標記為 No-Show!(未來該號碼或LINE將被收取訂金)"
 
 
 
103
  except Exception as e:
104
  return f"❌ 錯誤: {str(e)}"
105
 
@@ -155,7 +190,7 @@ def render_booking_cards():
155
 
156
 
157
  # ==========================================
158
- # 模組 2:菜單動態管理 (改為手動輸入圖片網址)
159
  # ==========================================
160
 
161
  def get_menu_items():
@@ -170,7 +205,6 @@ def get_menu_items():
170
  df['allow_takeout'] = df['allow_takeout'].apply(lambda x: "✅" if x else "❌")
171
  df['require_prepay'] = df['require_prepay'].apply(lambda x: "🔥 需預付" if x else "一般")
172
  df['is_active'] = df['is_active'].apply(lambda x: "🟢 販售中" if x else "🔴 已下架")
173
- # 簡單標示有沒有填網址
174
  df['has_image'] = df.get('image_url', pd.Series()).apply(lambda x: "🔗 有" if pd.notnull(x) and str(x).strip() else "無")
175
 
176
  display_df = df[['name', 'price', 'category', 'available_times', 'allow_takeout', 'require_prepay', 'is_active', 'has_image']]
@@ -181,53 +215,80 @@ def get_menu_items():
181
  return pd.DataFrame(columns=['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結'])
182
 
183
  def update_menu_dropdown():
184
- """更新上下架操作用的下拉選單,隱藏難記的 UUID"""
185
  try:
186
  res = supabase.table("menu_items").select("id, name, is_active").execute()
187
  if not res.data: return gr.update(choices=[])
188
- # 格式:[🟢販售中] 炭烤肋眼牛排 | uuid
189
  choices = [f"[{'🟢販售中' if item['is_active'] else '🔴已下架'}] {item['name']} | {item['id']}" for item in res.data]
190
  return gr.update(choices=choices)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  except:
192
- return gr.update(choices=[])
193
 
194
  def add_menu_item(name, desc, price, category, available_times, require_prepay, allow_takeout, image_url_input):
195
  if not name or not price: return "⚠️ 名稱與價格為必填", get_menu_items()
196
  if not available_times: return "⚠️ 請至少選擇一個供應時段", get_menu_items()
197
-
198
- # 處理圖片網址,確保空字串轉為 None 存入資料庫
199
  final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None
200
 
201
  try:
202
  data = {
203
- "name": name,
204
- "description": desc,
205
- "price": int(price),
206
- "category": category,
207
- "available_times": available_times,
208
- "allow_takeout": allow_takeout,
209
- "require_prepay": require_prepay,
210
- "is_active": True,
211
- "image_url": final_image_url # 寫入直接填寫的網址
212
  }
213
  supabase.table("menu_items").insert(data).execute()
214
- log_msg = f"✅ 成功新增餐點:{name}"
215
- if final_image_url:
216
- log_msg += "\n🔗 圖片網址已連結"
217
- return log_msg, get_menu_items()
218
- except Exception as e:
219
- return f"❌ 錯誤: {str(e)}", get_menu_items()
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
  def toggle_menu_item(selected_string, is_active):
222
  if not selected_string: return "⚠️ 請選擇餐點", get_menu_items()
223
  try:
224
- # 從選單字串中提取出藏在最後面的 UUID
225
  item_id = selected_string.split(" | ")[-1].strip()
226
  supabase.table("menu_items").update({"is_active": is_active}).eq("id", item_id).execute()
227
  status_text = "上架" if is_active else "下架"
228
  return f"✅ 餐點狀態已更新為:{status_text}", get_menu_items()
229
- except Exception as e:
230
- return f"❌ 錯誤: {str(e)}", get_menu_items()
231
 
232
 
233
  # ==========================================
@@ -247,6 +308,9 @@ body, .gradio-container { background-color: #0F0F0F; color: #fff; }
247
 
248
  with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochrome()) as demo:
249
 
 
 
 
250
  with gr.Group(visible=True) as login_row:
251
  gr.Markdown("# 🔒 老闆/管理員登入")
252
  with gr.Row():
@@ -264,7 +328,9 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
264
  with gr.Row():
265
  id_input = gr.Number(label="輸入訂單 ID", precision=0, scale=2)
266
  send_btn = gr.Button("🚀 發送提醒/確認", variant="primary", scale=1)
267
- noshow_btn = gr.Button("🚫 標記 No-Show", variant="stop", scale=1) # No-Show 按鈕
 
 
268
  refresh_btn = gr.Button("🔄 刷新列表", scale=1)
269
  log_output = gr.Textbox(label="執行結果日誌", lines=1)
270
 
@@ -273,25 +339,24 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
273
  # 按鈕事件
274
  refresh_btn.click(render_booking_cards, outputs=booking_display)
275
  send_btn.click(send_confirmation_hybrid, inputs=id_input, outputs=log_output).then(render_booking_cards, outputs=booking_display)
276
- noshow_btn.click(mark_no_show, inputs=id_input, outputs=log_output).then(render_booking_cards, outputs=booking_display)
 
277
 
278
  # --- 分頁 2:菜單動態管理 ---
279
  with gr.TabItem("🍽️ 菜單動態管理"):
280
- gr.Markdown("### ✨ 上架餐點")
 
 
 
 
281
  with gr.Row():
282
- # 左側:新增餐點表單
283
  with gr.Column(scale=1):
284
  m_name = gr.Textbox(label="餐點名稱 *")
285
  m_desc = gr.Textbox(label="餐點描述 (選填)")
286
  m_price = gr.Number(label="價格 (TWD) *", precision=0)
287
  m_cat = gr.Dropdown(choices=["main", "snack", "drink", "other"], label="分類", value="main")
288
-
289
- # --- 改良:手動填寫圖片網址 ---
290
- m_image_url = gr.Textbox(
291
- label="餐點照片網址 (選填)",
292
- placeholder="例如: https://ciecietaipei.github.io/assets/steak.jpg"
293
- )
294
-
295
  m_times = gr.CheckboxGroup(
296
  choices=["白天 (11:00-18:30)", "晚餐 (18:30-21:30)", "宵夜 (21:30後)"],
297
  value=["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], label="🕒 適用時段 *"
@@ -300,34 +365,54 @@ with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochro
300
  m_takeout = gr.Checkbox(label="🛍️ 開放外帶", value=True)
301
  m_prepay = gr.Checkbox(label="🔥 需全額預付", value=False)
302
 
303
- m_add_btn = gr.Button("➕ 新增上架", variant="primary")
304
- m_add_log = gr.Textbox(label="新增結果", interactive=False)
 
 
 
305
 
306
- # 右側:清單與防呆上下架
307
  with gr.Column(scale=2):
308
  gr.Markdown("### 📋 目前線上菜單")
309
  menu_df = gr.Dataframe(interactive=False, wrap=True)
310
  m_refresh_btn = gr.Button("🔄 刷新菜單")
311
 
312
- gr.Markdown("#### ⚙️ 快速上下架操作 (免記ID)")
313
  with gr.Row():
314
- m_toggle_dropdown = gr.Dropdown(label="選擇要操作的餐點", choices=[], scale=2)
315
- m_set_active = gr.Button("🟢 重新上架", scale=1)
316
- m_set_inactive = gr.Button("🔴 暫時下架", scale=1)
317
- m_toggle_log = gr.Textbox(label="操作結果", interactive=False)
318
-
319
- # 事件綁定
 
 
 
 
 
 
 
 
320
  m_add_btn.click(
321
  add_menu_item,
322
  inputs=[m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url],
323
- outputs=[m_add_log, menu_df]
 
 
 
 
 
 
324
  ).then(update_menu_dropdown, outputs=m_toggle_dropdown)
325
 
 
326
  m_refresh_btn.click(get_menu_items, outputs=menu_df).then(update_menu_dropdown, outputs=m_toggle_dropdown)
327
-
328
  m_set_active.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(True)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
329
  m_set_inactive.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(False)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
330
 
 
 
 
331
  # 登入事件
332
  login_btn.click(
333
  check_login, inputs=[username_input, password_input], outputs=[login_row, admin_tabs, error_msg]
 
12
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
13
  GAS_MAIL_URL = os.getenv("GAS_MAIL_URL")
14
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN")
15
+
16
+ # 改回使用 Hugging Face 的網址,作為信件點擊確認的端點
17
+ PUBLIC_SPACE_URL = os.getenv("HF_SPACE_URL", "https://deeplearning101-admin-ciecietaipei.hf.space")
18
 
19
  REAL_ADMIN_USER = os.getenv("ADMIN_USER") or "Deep Learning 101"
20
  REAL_ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") or "2016-11-11"
 
22
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
23
 
24
  # ==========================================
25
+ # 模組 0信件確認 Webhook (網址參數攔截)
26
+ # ==========================================
27
+ def check_url_action(request: gr.Request):
28
+ """當客人點擊信件裡的連結來到此頁面時,攔截 action 參數並處理,隱藏登入畫面"""
29
+ if not request: return gr.update(visible=False), gr.update(visible=True)
30
+
31
+ action = request.query_params.get('action')
32
+ bid = request.query_params.get('id')
33
+
34
+ if action == 'confirm' and bid:
35
+ try:
36
+ supabase.table("bookings").update({"status": "顧客已確認"}).eq("id", bid).execute()
37
+ msg = f"<div style='text-align:center; padding:50px; color:#2ecc71;'><h1>✅ 訂位已成功確認!</h1><p>感謝您的回覆,期待您的光臨。您現在可以關閉此視窗。</p></div>"
38
+ return gr.update(value=msg, visible=True), gr.update(visible=False)
39
+ except Exception as e:
40
+ return gr.update(value=f"❌ 處理失敗: {str(e)}", visible=True), gr.update(visible=False)
41
+
42
+ elif action == 'cancel' and bid:
43
+ try:
44
+ supabase.table("bookings").update({"status": "顧客已取消"}).eq("id", bid).execute()
45
+ msg = f"<div style='text-align:center; padding:50px; color:#e74c3c;'><h1>🚫 訂位已取消。</h1><p>期待下次為您服務。您現在可以關閉此視窗。</p></div>"
46
+ return gr.update(value=msg, visible=True), gr.update(visible=False)
47
+ except Exception as e:
48
+ return gr.update(value=f"❌ 處理失敗: {str(e)}", visible=True), gr.update(visible=False)
49
+
50
+ # 若沒有參數,則正常顯示老闆登入畫面
51
+ return gr.update(visible=False), gr.update(visible=True)
52
+
53
+
54
+ # ==========================================
55
+ # 模組 1:訂位與通知管理 (包含 No-Show 切換)
56
  # ==========================================
57
 
58
  def get_bookings():
 
66
 
67
  def send_confirmation_hybrid(booking_id):
68
  if not booking_id: return "❌ 請輸入訂單 ID"
 
69
  try:
70
  res = supabase.table("bookings").select("*").eq("id", booking_id).execute()
71
  if not res.data: return f"❌ 找不到 ID: {booking_id}"
 
125
  return log_msg
126
  except Exception as e: return f"嚴重錯誤: {str(e)}"
127
 
128
+ def toggle_no_show(booking_id, is_noshow=True):
129
+ """標記或撤銷 No-Show 狀態"""
130
  if not booking_id: return "❌ 請輸入訂單 ID"
131
+ status_text = "No-Show" if is_noshow else "待處理"
132
  try:
133
+ supabase.table("bookings").update({"status": status_text}).eq("id", booking_id).execute()
134
+ if is_noshow:
135
+ return f"🚫 訂單 {booking_id} 已成功標記為 No-Show 黑名單!"
136
+ else:
137
+ return f"✅ 訂單 {booking_id} 已撤銷 No-Show,恢復為「待處理」。"
138
  except Exception as e:
139
  return f"❌ 錯誤: {str(e)}"
140
 
 
190
 
191
 
192
  # ==========================================
193
+ # 模組 2:菜單動態管理 (支援編輯、修、上下架)
194
  # ==========================================
195
 
196
  def get_menu_items():
 
205
  df['allow_takeout'] = df['allow_takeout'].apply(lambda x: "✅" if x else "❌")
206
  df['require_prepay'] = df['require_prepay'].apply(lambda x: "🔥 需預付" if x else "一般")
207
  df['is_active'] = df['is_active'].apply(lambda x: "🟢 販售中" if x else "🔴 已下架")
 
208
  df['has_image'] = df.get('image_url', pd.Series()).apply(lambda x: "🔗 有" if pd.notnull(x) and str(x).strip() else "無")
209
 
210
  display_df = df[['name', 'price', 'category', 'available_times', 'allow_takeout', 'require_prepay', 'is_active', 'has_image']]
 
215
  return pd.DataFrame(columns=['餐點名稱', '價格', '分類', '供應時段', '可外帶', '預付規則', '狀態', '照片連結'])
216
 
217
  def update_menu_dropdown():
 
218
  try:
219
  res = supabase.table("menu_items").select("id, name, is_active").execute()
220
  if not res.data: return gr.update(choices=[])
 
221
  choices = [f"[{'🟢販售中' if item['is_active'] else '🔴已下架'}] {item['name']} | {item['id']}" for item in res.data]
222
  return gr.update(choices=choices)
223
+ except: return gr.update(choices=[])
224
+
225
+ # --- 新增:將選擇的餐點資料載入到左側表單 ---
226
+ def load_menu_data(selected_string):
227
+ if not selected_string:
228
+ return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
229
+
230
+ item_id = selected_string.split(" | ")[-1].strip()
231
+ try:
232
+ res = supabase.table("menu_items").select("*").eq("id", item_id).execute()
233
+ if not res.data: return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
234
+
235
+ item = res.data[0]
236
+ times = item.get("available_times", [])
237
+ if not times: times = ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"] # 預設值防呆
238
+
239
+ return (
240
+ item.get("name", ""),
241
+ item.get("description", ""),
242
+ item.get("price", 0),
243
+ item.get("category", "main"),
244
+ times,
245
+ item.get("require_prepay", False),
246
+ item.get("allow_takeout", True),
247
+ item.get("image_url", "") or "",
248
+ item_id # 將 ID 存入隱藏狀態中
249
+ )
250
  except:
251
+ return "", "", 0, "main", ["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], False, True, "", ""
252
 
253
  def add_menu_item(name, desc, price, category, available_times, require_prepay, allow_takeout, image_url_input):
254
  if not name or not price: return "⚠️ 名稱與價格為必填", get_menu_items()
255
  if not available_times: return "⚠️ 請至少選擇一個供應時段", get_menu_items()
 
 
256
  final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None
257
 
258
  try:
259
  data = {
260
+ "name": name, "description": desc, "price": int(price), "category": category,
261
+ "available_times": available_times, "allow_takeout": allow_takeout,
262
+ "require_prepay": require_prepay, "is_active": True, "image_url": final_image_url
 
 
 
 
 
 
263
  }
264
  supabase.table("menu_items").insert(data).execute()
265
+ return f"✅ 成功新增上架:{name}", get_menu_items()
266
+ except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items()
267
+
268
+ # --- 新增:儲存修改邏輯 ---
269
+ def update_menu_item(item_id, name, desc, price, category, available_times, require_prepay, allow_takeout, image_url_input):
270
+ if not item_id: return "⚠️ 請先從右側選擇並「載入編輯」一項餐點", get_menu_items()
271
+ if not name or not price: return "⚠️ 名稱與價格為必填", get_menu_items()
272
+ final_image_url = image_url_input.strip() if image_url_input and str(image_url_input).strip() else None
273
+
274
+ try:
275
+ data = {
276
+ "name": name, "description": desc, "price": int(price), "category": category,
277
+ "available_times": available_times, "allow_takeout": allow_takeout,
278
+ "require_prepay": require_prepay, "image_url": final_image_url
279
+ }
280
+ supabase.table("menu_items").update(data).eq("id", item_id).execute()
281
+ return f"💾 成功儲存修改:{name}", get_menu_items()
282
+ except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items()
283
 
284
  def toggle_menu_item(selected_string, is_active):
285
  if not selected_string: return "⚠️ 請選擇餐點", get_menu_items()
286
  try:
 
287
  item_id = selected_string.split(" | ")[-1].strip()
288
  supabase.table("menu_items").update({"is_active": is_active}).eq("id", item_id).execute()
289
  status_text = "上架" if is_active else "下架"
290
  return f"✅ 餐點狀態已更新為:{status_text}", get_menu_items()
291
+ except Exception as e: return f"❌ 錯誤: {str(e)}", get_menu_items()
 
292
 
293
 
294
  # ==========================================
 
308
 
309
  with gr.Blocks(title="Cié Cié Admin", css=custom_css, theme=gr.themes.Monochrome()) as demo:
310
 
311
+ # 網址參數處理 (信件確認 Webhook)
312
+ url_action_msg = gr.HTML(visible=False)
313
+
314
  with gr.Group(visible=True) as login_row:
315
  gr.Markdown("# 🔒 老闆/管理員登入")
316
  with gr.Row():
 
328
  with gr.Row():
329
  id_input = gr.Number(label="輸入訂單 ID", precision=0, scale=2)
330
  send_btn = gr.Button("🚀 發送提醒/確認", variant="primary", scale=1)
331
+ # No-Show 按鈕
332
+ noshow_btn = gr.Button("🚫 標記 No-Show", variant="stop", scale=1)
333
+ revert_noshow_btn = gr.Button("✅ 撤銷 No-Show", scale=1)
334
  refresh_btn = gr.Button("🔄 刷新列表", scale=1)
335
  log_output = gr.Textbox(label="執行結果日誌", lines=1)
336
 
 
339
  # 按鈕事件
340
  refresh_btn.click(render_booking_cards, outputs=booking_display)
341
  send_btn.click(send_confirmation_hybrid, inputs=id_input, outputs=log_output).then(render_booking_cards, outputs=booking_display)
342
+ noshow_btn.click(toggle_no_show, inputs=[id_input, gr.State(True)], outputs=log_output).then(render_booking_cards, outputs=booking_display)
343
+ revert_noshow_btn.click(toggle_no_show, inputs=[id_input, gr.State(False)], outputs=log_output).then(render_booking_cards, outputs=booking_display)
344
 
345
  # --- 分頁 2:菜單動態管理 ---
346
  with gr.TabItem("🍽️ 菜單動態管理"):
347
+ gr.Markdown("### ✨ 上架與編輯餐點")
348
+
349
+ # 隱藏狀態,用來紀錄正在編輯哪一筆餐點的 ID
350
+ m_edit_id = gr.State("")
351
+
352
  with gr.Row():
353
+ # 左側:餐點表單
354
  with gr.Column(scale=1):
355
  m_name = gr.Textbox(label="餐點名稱 *")
356
  m_desc = gr.Textbox(label="餐點描述 (選填)")
357
  m_price = gr.Number(label="價格 (TWD) *", precision=0)
358
  m_cat = gr.Dropdown(choices=["main", "snack", "drink", "other"], label="分類", value="main")
359
+ m_image_url = gr.Textbox(label="餐點照片網址 (選填)", placeholder="例如: https://ciecietaipei.github.io/assets/steak.jpg")
 
 
 
 
 
 
360
  m_times = gr.CheckboxGroup(
361
  choices=["白天 (11:00-18:30)", "晚餐 (18:30-21:30)", "宵夜 (21:30後)"],
362
  value=["晚餐 (18:30-21:30)", "宵夜 (21:30後)"], label="🕒 適用時段 *"
 
365
  m_takeout = gr.Checkbox(label="🛍️ 開放外帶", value=True)
366
  m_prepay = gr.Checkbox(label="🔥 需全額預付", value=False)
367
 
368
+ with gr.Row():
369
+ m_add_btn = gr.Button("➕ 作為餐點上架", variant="primary")
370
+ m_update_btn = gr.Button("💾 儲存修改 (需先載入)", variant="secondary")
371
+
372
+ m_form_log = gr.Textbox(label="執行結果", interactive=False)
373
 
374
+ # 右側:清單與操作
375
  with gr.Column(scale=2):
376
  gr.Markdown("### 📋 目前線上菜單")
377
  menu_df = gr.Dataframe(interactive=False, wrap=True)
378
  m_refresh_btn = gr.Button("🔄 刷新菜單")
379
 
380
+ gr.Markdown("#### ⚙️ 快速操作 (編輯與上下架)")
381
  with gr.Row():
382
+ m_toggle_dropdown = gr.Dropdown(label="選擇要操作的餐點", choices=[], scale=3)
383
+ m_load_btn = gr.Button("✏️ 載入編輯", scale=1)
384
+ m_set_active = gr.Button("🟢 架", scale=1)
385
+ m_set_inactive = gr.Button("🔴 下架", scale=1)
386
+ m_toggle_log = gr.Textbox(label="操作狀態", interactive=False)
387
+
388
+ # 事件綁定:載入編輯資料
389
+ m_load_btn.click(
390
+ load_menu_data,
391
+ inputs=[m_toggle_dropdown],
392
+ outputs=[m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url, m_edit_id]
393
+ ).then(lambda: "✅ 已載入至左側表單,修改後請點擊「儲存修改」", outputs=m_toggle_log)
394
+
395
+ # 事件綁定:新增 / 儲存修改
396
  m_add_btn.click(
397
  add_menu_item,
398
  inputs=[m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url],
399
+ outputs=[m_form_log, menu_df]
400
+ ).then(update_menu_dropdown, outputs=m_toggle_dropdown)
401
+
402
+ m_update_btn.click(
403
+ update_menu_item,
404
+ inputs=[m_edit_id, m_name, m_desc, m_price, m_cat, m_times, m_prepay, m_takeout, m_image_url],
405
+ outputs=[m_form_log, menu_df]
406
  ).then(update_menu_dropdown, outputs=m_toggle_dropdown)
407
 
408
+ # 事件綁定:刷新與上下架
409
  m_refresh_btn.click(get_menu_items, outputs=menu_df).then(update_menu_dropdown, outputs=m_toggle_dropdown)
 
410
  m_set_active.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(True)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
411
  m_set_inactive.click(toggle_menu_item, inputs=[m_toggle_dropdown, gr.State(False)], outputs=[m_toggle_log, menu_df]).then(update_menu_dropdown, outputs=m_toggle_dropdown)
412
 
413
+ # 頁面載入攔截 URL 參數
414
+ demo.load(check_url_action, inputs=None, outputs=[url_action_msg, login_row])
415
+
416
  # 登入事件
417
  login_btn.click(
418
  check_login, inputs=[username_input, password_input], outputs=[login_row, admin_tabs, error_msg]