Spaces:
Sleeping
Sleeping
File size: 4,863 Bytes
e66cfb4 ea68c55 e66cfb4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | """
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),
)
@callback(
Output("student-id-store", "data", allow_duplicate=True),
Input("student-logout-btn", "n_clicks"),
prevent_initial_call=True,
)
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:按下「開始學習」後儲存學號 ──────────────────────────
@callback(
Output("student-id-store", "data"),
Output("student-login-error", "children"),
Output("student-login-error", "style"),
Input("student-login-btn", "n_clicks"),
State("student-id-input", "value"),
prevent_initial_call=True,
)
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 更新 ─────────────────────────────────────────────────────
@callback(
Output("student-badge", "style"),
Output("student-badge-text", "children"),
Input("student-id-store", "data"),
)
def update_badge(student_id):
if student_id:
return {"display": "inline-flex", "alignItems": "center"}, f"學號:{student_id}"
return {"display": "none"}, ""
|