DeepLearning101 commited on
Commit
b51a54f
·
verified ·
1 Parent(s): f0da861

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -56
app.py CHANGED
@@ -29,9 +29,9 @@ def get_bookings():
29
  except:
30
  return pd.DataFrame()
31
 
32
- # 🔥🔥🔥 後端發送邏輯 (暴力發送) 🔥🔥🔥
33
  def send_confirmation_hybrid(booking_id):
34
- print(f"收到後端請求,ID: {booking_id}") # Server log
35
  if not booking_id: return "❌ 錯誤:未讀取到 ID (Backend received None)"
36
 
37
  try:
@@ -49,11 +49,10 @@ def send_confirmation_hybrid(booking_id):
49
 
50
  log_msg = f"🆔 {booking_id}: "
51
 
52
- # 判斷內容
53
  is_reminder = "確認" in current_status
54
 
55
  if is_reminder:
56
- # 提醒信
57
  action_label = "提醒"
58
  mail_subject = f"🔔 訂位提醒: {booking['date']} - Cié Cié Taipei"
59
  line_text = (
@@ -67,7 +66,7 @@ def send_confirmation_hybrid(booking_id):
67
  mail_html = f"""
68
  <div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px;">
69
  <h2 style="text-align:center; border-bottom:1px solid #444;">Cié Cié Taipei</h2>
70
- <p style="color:#eee;"><strong>{booking['name']}</strong> 您好,期待您的光臨!這是一封行前提醒。</p>
71
  <div style="background:#2a2a2a; padding:15px; margin:20px 0; border-left:4px solid #f1c40f;">
72
  <ul style="color:#ddd;">
73
  <li>📅 日期:{booking['date']}</li>
@@ -81,7 +80,6 @@ def send_confirmation_hybrid(booking_id):
81
  </div>
82
  """
83
  else:
84
- # 確認信
85
  action_label = "確認"
86
  mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
87
  line_text = (
@@ -136,7 +134,7 @@ def send_confirmation_hybrid(booking_id):
136
 
137
  except Exception as e: return f"🔥 嚴重錯誤: {str(e)}"
138
 
139
- # --- 卡片渲染 ---
140
  def render_booking_cards():
141
  df = get_bookings()
142
  count_html = f"<div style='color:#bbb; margin-bottom:20px; text-align:right; font-size:16px; padding: 0 10px;'>📊 共找到 <span style='color:#fff; font-weight:bold;'>{len(df)}</span> 筆資料</div>"
@@ -147,7 +145,6 @@ def render_booking_cards():
147
  for index, row in df.iterrows():
148
  status = row.get('status', '待處理')
149
 
150
- # 狀態與顏色
151
  status_color = "#ccc"; border_color = "#444"
152
  if '確認' in status: status_color = "#2ecc71"; border_color = "#2ecc71"
153
  elif '取消' in status: status_color = "#e74c3c"; border_color = "#e74c3c"
@@ -156,10 +153,10 @@ def render_booking_cards():
156
  is_canceled = '取消' in status
157
  is_confirmed = '確認' in status
158
 
159
- # JS 綁定
160
- btn_onclick = "" if is_canceled else f"cardAction({row['id']})"
 
161
 
162
- # 按鈕外觀
163
  if is_canceled:
164
  btn_style = "background: #333; color: #666; cursor: not-allowed;"
165
  btn_text = "🚫 已取消"
@@ -224,7 +221,10 @@ def render_booking_cards():
224
  <div style="font-size: 1.1em; color: #000; font-weight: 900; background: #e0e0e0; padding: 8px 12px; border-radius: 6px; font-family: monospace;">
225
  ID: {row['id']}
226
  </div>
227
- <button onclick="{btn_onclick}" style="border: none; padding: 14px 30px; border-radius: 8px; font-size: 1.1em; transition: all 0.2s; min-width: 150px; cursor: pointer; {btn_style}">
 
 
 
228
  {btn_text}
229
  </button>
230
  </div>
@@ -245,15 +245,62 @@ def check_login(user, password):
245
  }
246
  else: return {error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>"}
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- # --- CSS ---
250
  custom_css = """
251
  body, .gradio-container { background-color: #0F0F0F; color: #fff; }
252
  #booking_display { height: auto !important; max-height: none !important; overflow: visible !important; margin-bottom: 50px; }
253
  button:active { transform: scale(0.96); }
254
  #header-panel { background: #1a1a1a; padding: 15px; margin-bottom: 20px; border-radius: 10px; }
255
 
256
- /* 🔥 隱藏操作區:用 CSS 把元件移出畫面,確保 JS 仍然可以存取 */
 
 
 
257
  #hidden_ops {
258
  position: absolute !important;
259
  left: -9999px !important;
@@ -264,50 +311,10 @@ button:active { transform: scale(0.96); }
264
  }
265
  """
266
 
267
- # 🔥🔥🔥 暴力注入 JS:直接寫在 HTML 裡,確保 cardAction 絕對全域可用 🔥🔥🔥
268
- # 這段代碼會被直接渲染到網頁頂部,解決 ReferenceError
269
- GLOBAL_JS = """
270
- <script>
271
- console.log("✨ 訂位系統腳本已載入 (v5.1)");
272
-
273
- // 直接定義在 window 物件上,保證全域可見
274
- window.cardAction = function(id) {
275
- console.log("👉 按下 ID:", id);
276
-
277
- // 1. 找輸入框 (相容 input 和 textarea)
278
- let idInput = document.querySelector('#hidden_id_input input');
279
-
280
- if (idInput) {
281
- console.log("✅ 找到隱藏輸入框,寫入 ID...");
282
- // 強制寫入值
283
- idInput.value = id;
284
- // 觸發 React/Gradio 的更新事件
285
- let event = new Event('input', { bubbles: true });
286
- idInput.dispatchEvent(event);
287
-
288
- // 2. 觸發按鈕
289
- setTimeout(() => {
290
- let sendBtn = document.querySelector('#hidden_send_btn');
291
- if (sendBtn) {
292
- console.log("🚀 觸發發送按鈕...");
293
- sendBtn.click();
294
- } else {
295
- console.error("❌ 找不到發送按鈕 (#hidden_send_btn)");
296
- alert("系統錯誤:找不到發送按鈕,請重新整理頁面");
297
- }
298
- }, 200); // 延遲 200ms 確保數值已更新
299
- } else {
300
- console.error("❌ 找不到隱藏輸入框 (#hidden_id_input)");
301
- alert("系統錯誤:找不到輸入框,請重新整理頁面");
302
- }
303
- }
304
- </script>
305
- """
306
-
307
  # --- 介面 ---
308
  with gr.Blocks(title="Admin") as demo:
309
 
310
- # 注入 JS 腳本 (直接放在最上面)
311
  gr.HTML(GLOBAL_JS)
312
 
313
  with gr.Group(visible=True) as login_row:
@@ -325,7 +332,7 @@ with gr.Blocks(title="Admin") as demo:
325
 
326
  booking_display = gr.HTML(elem_id="booking_display")
327
 
328
- # 🔥 隱藏操作區:visible=True 但被 CSS 移出畫面
329
  with gr.Column(visible=True, elem_id="hidden_ops"):
330
  hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
331
  hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")
 
29
  except:
30
  return pd.DataFrame()
31
 
32
+ # 🔥🔥🔥 發送邏輯 (暴力發送) 🔥🔥🔥
33
  def send_confirmation_hybrid(booking_id):
34
+ print(f"收到後端請求,ID: {booking_id}")
35
  if not booking_id: return "❌ 錯誤:未讀取到 ID (Backend received None)"
36
 
37
  try:
 
49
 
50
  log_msg = f"🆔 {booking_id}: "
51
 
52
+ # 判斷是提醒還是確認
53
  is_reminder = "確認" in current_status
54
 
55
  if is_reminder:
 
56
  action_label = "提醒"
57
  mail_subject = f"🔔 訂位提醒: {booking['date']} - Cié Cié Taipei"
58
  line_text = (
 
66
  mail_html = f"""
67
  <div style="background:#1a1a1a; color:#d4af37; padding:20px; border-radius:8px;">
68
  <h2 style="text-align:center; border-bottom:1px solid #444;">Cié Cié Taipei</h2>
69
+ <p style="color:#eee;"><strong>{booking['name']}</strong> 您好,行前提醒:</p>
70
  <div style="background:#2a2a2a; padding:15px; margin:20px 0; border-left:4px solid #f1c40f;">
71
  <ul style="color:#ddd;">
72
  <li>📅 日期:{booking['date']}</li>
 
80
  </div>
81
  """
82
  else:
 
83
  action_label = "確認"
84
  mail_subject = f"訂位確認: {booking['date']} - Cié Cié Taipei"
85
  line_text = (
 
134
 
135
  except Exception as e: return f"🔥 嚴重錯誤: {str(e)}"
136
 
137
+ # 🔥🔥🔥 卡片渲染 (修正:不使用 onclick,改用 data-id) 🔥🔥🔥
138
  def render_booking_cards():
139
  df = get_bookings()
140
  count_html = f"<div style='color:#bbb; margin-bottom:20px; text-align:right; font-size:16px; padding: 0 10px;'>📊 共找到 <span style='color:#fff; font-weight:bold;'>{len(df)}</span> 筆資料</div>"
 
145
  for index, row in df.iterrows():
146
  status = row.get('status', '待處理')
147
 
 
148
  status_color = "#ccc"; border_color = "#444"
149
  if '確認' in status: status_color = "#2ecc71"; border_color = "#2ecc71"
150
  elif '取消' in status: status_color = "#e74c3c"; border_color = "#e74c3c"
 
153
  is_canceled = '取消' in status
154
  is_confirmed = '確認' in status
155
 
156
+ # 🟢 修正:移除 onclick,改用 class 和 data-id 屬性
157
+ # 這樣就不會依賴全域函式是否存在,而是靠下方的監聽器
158
+ btn_attrs = f"class='op-btn' data-id='{row['id']}'" if not is_canceled else "disabled"
159
 
 
160
  if is_canceled:
161
  btn_style = "background: #333; color: #666; cursor: not-allowed;"
162
  btn_text = "🚫 已取消"
 
221
  <div style="font-size: 1.1em; color: #000; font-weight: 900; background: #e0e0e0; padding: 8px 12px; border-radius: 6px; font-family: monospace;">
222
  ID: {row['id']}
223
  </div>
224
+
225
+ <button {btn_attrs} style="
226
+ border: none; padding: 14px 30px; border-radius: 8px; font-size: 1.1em;
227
+ transition: all 0.2s; min-width: 150px; cursor: pointer; {btn_style}">
228
  {btn_text}
229
  </button>
230
  </div>
 
245
  }
246
  else: return {error_msg: "<span style='color: red'>❌ 帳號或密碼錯誤</span>"}
247
 
248
+ # 🔥🔥🔥 絕對有效的事件監聽器 (Event Delegation) 🔥🔥🔥
249
+ # 這次不是定義函式,而是直接監聽整個文件的點擊事件。
250
+ # 只要點擊了 class="op-btn" 的元素,就執行。這不依賴作用域,一定有效。
251
+ GLOBAL_JS = """
252
+ <script>
253
+ document.addEventListener('DOMContentLoaded', function() {
254
+ console.log("🟢 訂位系統監聽器已啟動");
255
+ });
256
+
257
+ // 全域監聽點擊 (Event Delegation)
258
+ document.addEventListener('click', function(e) {
259
+ // 檢查點擊的目標是否是我們的操作按鈕
260
+ if (e.target && e.target.classList.contains('op-btn')) {
261
+ const id = e.target.getAttribute('data-id');
262
+ console.log("👉 偵測到按鈕點擊,ID:", id);
263
+
264
+ if (!id) return;
265
+
266
+ // 1. 找輸入框
267
+ const idInput = document.querySelector('#hidden_id_input input');
268
+
269
+ if (idInput) {
270
+ console.log("✅ 寫入 ID 到 Gradio...");
271
+ idInput.value = id;
272
+ idInput.dispatchEvent(new Event('input', { bubbles: true }));
273
+
274
+ // 2. 觸發發送按鈕
275
+ setTimeout(() => {
276
+ const sendBtn = document.querySelector('#hidden_send_btn');
277
+ if (sendBtn) {
278
+ console.log("🚀 觸發 Python 發送邏輯...");
279
+ sendBtn.click();
280
+ } else {
281
+ console.error("❌ 找不到 hidden_send_btn");
282
+ }
283
+ }, 100);
284
+ } else {
285
+ console.error("❌ 找不到 hidden_id_input");
286
+ alert("系統錯誤:找不到對應的輸入框,請重新整理頁面。");
287
+ }
288
+ }
289
+ });
290
+ </script>
291
+ """
292
 
293
+ # --- CSS (確保按鈕可按) ---
294
  custom_css = """
295
  body, .gradio-container { background-color: #0F0F0F; color: #fff; }
296
  #booking_display { height: auto !important; max-height: none !important; overflow: visible !important; margin-bottom: 50px; }
297
  button:active { transform: scale(0.96); }
298
  #header-panel { background: #1a1a1a; padding: 15px; margin-bottom: 20px; border-radius: 10px; }
299
 
300
+ /* 讓操作按鈕有點擊指標 */
301
+ .op-btn { pointer-events: auto !important; }
302
+
303
+ /* 隱藏操作區:移出畫面外 */
304
  #hidden_ops {
305
  position: absolute !important;
306
  left: -9999px !important;
 
311
  }
312
  """
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  # --- 介面 ---
315
  with gr.Blocks(title="Admin") as demo:
316
 
317
+ # 注入 JS (直接執行,不包在 function 裡)
318
  gr.HTML(GLOBAL_JS)
319
 
320
  with gr.Group(visible=True) as login_row:
 
332
 
333
  booking_display = gr.HTML(elem_id="booking_display")
334
 
335
+ # 隱藏操作區
336
  with gr.Column(visible=True, elem_id="hidden_ops"):
337
  hidden_id_input = gr.Number(elem_id="hidden_id_input", precision=0)
338
  hidden_send_btn = gr.Button("Send", elem_id="hidden_send_btn")