1use dioxus::prelude::*;
8use std::sync::Arc;
9
10use crate::components::admin_layout::AdminLayout;
11use crate::components::frontend_layout::FrontendLayout;
12use crate::context::UserContext;
13use crate::pages::about::About;
14use crate::pages::admin::{
15 Admin, AdminComments, AdminCommentsPage, Assets, FriendsAdmin, Mcp, Posts, Runner, System,
16 Write, WriteEdit,
17};
18use crate::pages::archives::Archives;
19use crate::pages::changelog::Changelog;
20use crate::pages::friends::Friends;
21use crate::pages::home::{Home, HomePage};
22use crate::pages::login::Login;
23use crate::pages::not_found::NotFound;
24use crate::pages::post_detail::PostDetail;
25use crate::pages::register::Register;
26use crate::pages::search::Search;
27use crate::pages::tags::{TagDetail, Tags};
28use crate::theme::{use_theme_provider, ThemePreload};
29
30#[derive(Clone, Routable, Debug, PartialEq)]
32#[rustfmt::skip]
33pub enum Route {
34 #[layout(ErrorLayout)]
36 #[layout(FrontendLayout)]
37 #[route("/")]
39 Home {},
40 #[route("/page/:page")]
42 HomePage { page: i32 },
43 #[route("/archives")]
45 Archives {},
46 #[route("/tags")]
48 Tags {},
49 #[route("/tags/:tag")]
51 TagDetail { tag: String },
52 #[route("/post/:slug")]
54 PostDetail { slug: String },
55 #[route("/search")]
57 Search {},
58 #[route("/about")]
60 About {},
61 #[route("/friends")]
63 Friends {},
64 #[route("/changelog")]
66 Changelog {},
67 #[route("/:..segments")]
69 NotFound { segments: Vec<String> },
70 #[end_layout]
71 #[end_layout]
72
73 #[nest("/admin")]
75 #[layout(AdminLayout)]
77 #[route("/")]
79 Admin {},
80 #[route("/write")]
82 Write {},
83 #[route("/write/:id")]
85 WriteEdit { id: i32 },
86 #[route("/posts")]
88 Posts {},
89 #[route("/assets")]
91 Assets {},
92 #[route("/friends")]
94 FriendsAdmin {},
95 #[route("/comments")]
97 AdminComments {},
98 #[route("/comments/:page")]
100 AdminCommentsPage { page: i32 },
101 #[route("/system")]
103 System {},
104 #[route("/runner")]
106 Runner {},
107 #[route("/mcp")]
109 Mcp {},
110 #[end_layout]
111 #[end_nest]
112
113 #[route("/login")]
115 Login {},
116 #[route("/register")]
118 Register {},
119}
120
121#[component]
125pub fn AppRouter() -> Element {
126 let _theme = use_theme_provider();
127
128 let user = use_signal(|| None::<Arc<crate::models::user::PublicUser>>);
130 let checked = use_signal(|| false);
131 use_context_provider(|| UserContext { user, checked });
132
133 rsx! {
134 document::Stylesheet { href: "/style.css" }
135 document::Stylesheet { href: "/highlight.css" }
136 document::Title { "Yggdrasil Blog" }
137 document::Link { rel: "icon", href: "/favicon.ico" }
138 div {
139 ThemePreload {}
140 Router::<Route> {}
141 }
142 }
143}
144
145#[component]
146fn ErrorLayout() -> Element {
147 rsx! {
148 ErrorBoundary {
149 handle_error: move |err: ErrorContext| {
150 let err_ctx = err.clone();
155 #[cfg(feature = "server")]
157 {
158 if let Some(captured_error) = err.error() {
159 let _ = dioxus::fullstack::FullstackContext::commit_error_status(
160 captured_error,
161 );
162 }
163 }
164 let mut is_404 = false;
165 if let Some(captured_error) = err.error() {
166 if let Some(ServerFnError::ServerError { code, message, .. }) = captured_error
167 .downcast_ref::<ServerFnError>()
168
169 {
170 if *code == 404 || message == "not_found" {
171 is_404 = true;
172 }
173 } else { let err_str = format!("{:?}", captured_error);
175 if err_str.contains("NotFound") || err_str.contains("404")
176 || err_str.contains("not found")
177 {
178 is_404 = true;
179 }
180 }
181 }
182 if is_404 {
183 rsx! {
184 NotFound { segments: vec![] }
185 }
186 } else {
187 rsx! {
188 div { class: "flex flex-col items-center justify-center min-h-[50vh] md:min-h-[55vh] px-6 animate-page-enter",
189 div { class: "w-full max-w-md bg-paper-entry border border-paper-border rounded-[2rem] p-8 md:p-10 shadow-sm hover:border-paper-secondary transition-all duration-300 flex flex-col items-center text-center",
190 div { class: "relative mb-6",
192 div { class: "w-16 h-16 bg-red-500/10 dark:bg-red-500/20 text-red-500 rounded-full flex items-center justify-center",
193 svg {
194 xmlns: "http://www.w3.org/2000/svg",
195 width: "28",
196 height: "28",
197 view_box: "0 0 24 24",
198 fill: "none",
199 stroke: "currentColor",
200 stroke_width: "2",
201 stroke_linecap: "round",
202 stroke_linejoin: "round",
203 circle { cx: "12", cy: "12", r: "10" }
204 line {
205 x1: "12",
206 y1: "8",
207 x2: "12",
208 y2: "12",
209 }
210 line {
211 x1: "12",
212 y1: "16",
213 x2: "12.01",
214 y2: "16",
215 }
216 }
217 }
218 }
219
220 h1 { class: "text-lg md:text-xl font-bold tracking-tight text-paper-primary mb-3",
222 "加载失败"
223 }
224 p { class: "text-sm text-paper-secondary leading-relaxed mb-8 max-w-[280px]",
225 "抱歉,加载页面时出现了一些错误,请稍后再试。"
226 }
227
228 button {
232 r#type: "button",
233 onclick: {
234 let err_ctx = err_ctx.clone();
235 move |_| {
236 err_ctx.clear_errors();
237 let _ = dioxus::router::navigator().push(Route::Home {});
238 }
239 },
240 class: "group inline-flex items-center gap-2 px-6 py-2.5 text-sm font-medium text-paper-primary bg-paper-theme border border-paper-border rounded-full hover:border-paper-secondary hover:bg-paper-border transition-all duration-200 cursor-pointer shadow-sm active:scale-[0.98]",
241 svg {
242 xmlns: "http://www.w3.org/2000/svg",
243 width: "16",
244 height: "16",
245 view_box: "0 0 24 24",
246 fill: "none",
247 stroke: "currentColor",
248 stroke_width: "2",
249 stroke_linecap: "round",
250 stroke_linejoin: "round",
251 class: "transition-transform group-hover:-translate-x-0.5",
252 path { d: "M19 12H5M12 19l-7-7 7-7" }
253 }
254 "返回首页"
255 }
256 }
257 }
258 }
259 }
260 },
261 Outlet::<Route> {}
262 }
263 }
264}