Skip to main content

yggdrasil/pages/
login.rs

1//! 登录页面
2//!
3//! 提供用户名/密码表单,前端校验通过后调用 `login` server function,
4//! 登录成功时跳转到管理后台首页。
5
6use dioxus::prelude::*;
7use dioxus::router::components::Link;
8
9use crate::api::auth::{login, AuthResponse};
10use crate::components::forms::{AlertBox, FormInput, FormLabel, BUTTON_PRIMARY_CLASS};
11use crate::context::UserContext;
12use crate::router::Route;
13
14/// 登录页面组件
15#[component]
16pub fn Login() -> Element {
17    // 表单输入状态
18    let mut username = use_signal(|| "".to_string());
19    let mut password = use_signal(|| "".to_string());
20    // 错误提示与加载状态
21    let mut error = use_signal(|| None::<String>);
22    let mut loading = use_signal(|| false);
23    // 全局用户上下文,用于触发登录后的状态刷新
24    let mut ctx: UserContext = use_context();
25
26    // 提交登录表单
27    let on_submit = Callback::new(move |_| {
28        if loading() {
29            return;
30        }
31        error.set(None);
32        loading.set(true);
33
34        let username_val = username();
35        let password_val = password();
36
37        // 在异步任务中调用 server function 登录
38        spawn(async move {
39            match login(username_val, password_val).await {
40                Ok(AuthResponse { success: true, .. }) => {
41                    // 登录成功:重置上下文检查标记并跳转到后台
42                    ctx.checked.set(false);
43                    let _ = dioxus::router::navigator().push(Route::Admin {});
44                }
45                Ok(AuthResponse {
46                    success: false,
47                    message,
48                    ..
49                }) => {
50                    error.set(Some(message));
51                }
52                Err(e) => {
53                    error.set(Some(format!("请求失败: {}", e)));
54                }
55            }
56            loading.set(false);
57        });
58    });
59
60    let is_loading = loading();
61
62    rsx! {
63        div { class: "min-h-screen flex items-center justify-center bg-paper-theme",
64            div { class: "w-full max-w-md p-8 bg-paper-entry rounded-2xl border border-paper-border shadow-sm",
65                h1 { class: "text-2xl font-bold text-center text-paper-primary mb-6",
66                    "登录"
67                }
68
69                if let Some(err) = error() {
70                    AlertBox { message: err, variant: "error" }
71                }
72
73                div { class: "space-y-4",
74                    div {
75                        FormLabel {
76                            label: "用户名 / 邮箱",
77                            html_for: Some("login-username".to_string()),
78                        }
79                        FormInput {
80                            id: Some("login-username".to_string()),
81                            r#type: "text",
82                            placeholder: "用户名或邮箱",
83                            value: username(),
84                            disabled: is_loading,
85                            oninput: move |v: String| username.set(v),
86                            // 回车键触发提交
87                            onkeydown: Some(
88                                EventHandler::new(move |e: KeyboardEvent| {
89                                    if e.key() == Key::Enter {
90                                        on_submit(())
91                                    }
92                                }),
93                            ),
94                        }
95                    }
96                    div {
97                        FormLabel {
98                            label: "密码",
99                            html_for: Some("login-password".to_string()),
100                        }
101                        FormInput {
102                            id: Some("login-password".to_string()),
103                            r#type: "password",
104                            placeholder: "密码",
105                            value: password(),
106                            disabled: is_loading,
107                            oninput: move |v: String| password.set(v),
108                            onkeydown: Some(
109                                EventHandler::new(move |e: KeyboardEvent| {
110                                    if e.key() == Key::Enter {
111                                        on_submit(())
112                                    }
113                                }),
114                            ),
115                        }
116                    }
117                    button {
118                        class: "{BUTTON_PRIMARY_CLASS}",
119                        class: if is_loading { "opacity-60 cursor-not-allowed" },
120                        disabled: is_loading,
121                        onclick: move |_| on_submit(()),
122                        if is_loading {
123                            "登录中..."
124                        } else {
125                            "登录"
126                        }
127                    }
128                    Link {
129                        class: "block w-full py-2 px-4 text-center text-paper-secondary hover:text-paper-accent font-medium rounded-lg transition-all duration-200 cursor-pointer",
130                        to: Route::Register {},
131                        "还没有账号?去注册"
132                    }
133                }
134            }
135        }
136    }
137}