Spaces:
Sleeping
Sleeping
| """ | |
| student_login.py | |
| ---------------- | |
| 學生登入元件。 | |
| 用 clientside callback 直接讀 localStorage,避免 Python callback 時序問題。 | |
| """ | |
| from __future__ import annotations | |
| import dash | |
| from dash import html, dcc, callback, Input, Output, State, no_update, clientside_callback | |
| import dash_mantine_components as dmc | |
| from dash_iconify import DashIconify | |
| def create_student_store() -> dcc.Store: | |
| return dcc.Store(id="student-id-store", storage_type="local", data="") | |
| def create_login_modal() -> html.Div: | |
| return html.Div([ | |
| dmc.Modal( | |
| id="student-login-modal", | |
| opened=False, | |
| centered=True, | |
| closeOnClickOutside=False, | |
| closeOnEscape=False, | |
| withCloseButton=False, | |
| title=dmc.Group([ | |
| DashIconify(icon="tabler:school", height=24), | |
| dmc.Text("歡迎使用數學學習平台", fw=700, size="lg"), | |
| ], gap="xs"), | |
| children=[ | |
| dmc.Text( | |
| "請輸入您的學號,系統將記錄您的學習進度。", | |
| size="sm", c="dimmed", mb="md", | |
| ), | |
| dmc.TextInput( | |
| id="student-id-input", | |
| label="學號", | |
| placeholder="例如:B11234567", | |
| leftSection=DashIconify(icon="tabler:id-badge", height=16), | |
| required=True, | |
| mb="md", | |
| ), | |
| dmc.Alert( | |
| id="student-login-error", | |
| children="", | |
| color="red", | |
| variant="light", | |
| style={"display": "none"}, | |
| mb="sm", | |
| ), | |
| dmc.Button( | |
| "開始學習", | |
| id="student-login-btn", | |
| fullWidth=True, | |
| leftSection=DashIconify(icon="tabler:arrow-right", height=16), | |
| ), | |
| ], | |
| size="sm", | |
| ), | |
| ]) | |
| def create_student_badge() -> html.Div: | |
| return html.Div( | |
| id="student-badge", | |
| style={"display": "none"}, | |
| children=dmc.Group([ | |
| dmc.Badge( | |
| id="student-badge-text", | |
| leftSection=DashIconify(icon="tabler:user", height=12), | |
| variant="light", | |
| color="teal", | |
| size="sm", | |
| ), | |
| dmc.ActionIcon( | |
| DashIconify(icon="tabler:logout", height=14), | |
| id="student-logout-btn", | |
| size="sm", | |
| variant="subtle", | |
| color="gray", | |
| ), | |
| ], gap=4), | |
| ) | |
| def logout_student(n_clicks): | |
| if n_clicks: | |
| return "" | |
| return no_update | |
| # ─── Clientside:頁面載入時直接從 localStorage 判斷是否開啟 modal ─────────── | |
| # 完全在瀏覽器端執行,不需要等 Python callback,沒有時序問題 | |
| clientside_callback( | |
| """ | |
| function(storeData) { | |
| try { | |
| if (storeData && storeData !== "") { | |
| return false; | |
| } | |
| return true; | |
| } catch(e) { | |
| return true; | |
| } | |
| } | |
| """, | |
| Output("student-login-modal", "opened"), | |
| Input("student-id-store", "data"), | |
| prevent_initial_call=False, | |
| ) | |
| # ─── Python callback:按下「開始學習」後儲存學號 ────────────────────────── | |
| def save_student_id(n_clicks, input_value): | |
| sid = (input_value or "").strip() | |
| if not sid: | |
| return no_update, "請輸入學號", {"display": "block"} | |
| # 寫入 Firebase | |
| try: | |
| from firebase_progress import upsert_student | |
| upsert_student(sid) | |
| except Exception as e: | |
| print(f"[Firebase] upsert_student 失敗:{e}") | |
| return sid, "", {"display": "none"} | |
| # ─── 學生 badge 更新 ───────────────────────────────────────────────────── | |
| def update_badge(student_id): | |
| if student_id: | |
| return {"display": "inline-flex", "alignItems": "center"}, f"學號:{student_id}" | |
| return {"display": "none"}, "" | |