yggdrasil/src/components/admin_layout.rs
xfy d3c5653808 feat: redesign admin write page with fixed-height editor layout
- Redesign write page UI with cleaner, more immersive editing experience
- Use CSS variables for theme-aware styling (paper-theme colors)
- Make editor fill remaining viewport height with internal scrolling
- Move action bar below editor (left-aligned, non-floating)
- Fix layout: page no longer scrolls, only editor content scrolls
- Update write skeleton to match new layout
- Update admin layout to use h-dvh for write routes to prevent page scroll
- Fix Dioxus.toml dev resource loading to include style.css

Fixes from review:
- Add missing relative positioning for loading overlay
- Use consistent root_class in unauthenticated state
- Use h-dvh instead of h-screen for mobile viewport compatibility
- Ensure skeleton matches actual component layout classes
2026-06-10 17:41:31 +08:00

130 lines
4.6 KiB
Rust

use dioxus::prelude::*;
use crate::api::auth::{get_current_user, logout};
use crate::components::admin_skeleton::AdminDashboardSkeleton;
use crate::components::footer::Footer;
use crate::components::header::{Header, NavItemConfig};
use crate::components::write_skeleton::WriteSkeleton;
use crate::context::UserContext;
use crate::hooks::delayed_loading::use_delayed_loading;
use crate::router::Route;
use crate::theme::ThemeToggle;
#[component]
pub fn AdminLayout() -> Element {
let mut ctx: UserContext = use_context();
let navigator = dioxus::router::navigator();
let route = use_route::<Route>();
let show_skeleton = use_delayed_loading(move || !(ctx.checked)());
use_effect(move || {
if !(ctx.checked)() {
(ctx.checked).set(true);
spawn(async move {
match get_current_user().await {
Ok(response) => {
if let Some(user) = response.user {
ctx.user.set(Some(std::sync::Arc::new(user)));
} else {
let _ = navigator.push(Route::Login {});
}
}
Err(_) => {
let _ = navigator.push(Route::Login {});
}
}
});
}
});
let admin_nav_items = vec![
NavItemConfig {
route: Route::Admin {},
label: "仪表盘",
is_active: matches!(route, Route::Admin {}),
},
NavItemConfig {
route: Route::Write {},
label: "写文章",
is_active: matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. }),
},
NavItemConfig {
route: Route::Posts {},
label: "管理文章",
is_active: matches!(route, Route::Posts {}),
},
];
let right_content = rsx! {
div { class: "flex items-center gap-3",
ThemeToggle {}
button {
class: "text-sm text-gray-600 dark:text-[#9b9c9d] hover:text-gray-900 dark:hover:text-[#dadadb] transition-colors",
onclick: move |_| {
spawn(async move {
let _ = logout().await;
ctx.user.set(None);
ctx.checked.set(false);
let _ = navigator.push(Route::Login {});
});
},
"登出"
}
}
};
let is_write_route = matches!(route, Route::Write {}) || matches!(route, Route::WriteEdit { .. });
let main_class = if is_write_route {
"flex-1 w-full max-w-5xl mx-auto px-6 flex flex-col overflow-hidden"
} else {
"flex-1 w-full max-w-5xl mx-auto px-6 py-8"
};
// Write 路由:页面固定高度,不滚动,由编辑器内部处理滚动
let root_class = if is_write_route {
"h-dvh flex flex-col overflow-hidden bg-white dark:bg-[#1d1e20]"
} else {
"min-h-screen flex flex-col bg-white dark:bg-[#1d1e20]"
};
match ((ctx.checked)(), (ctx.user)()) {
(true, Some(_)) => {
rsx! {
div { class: "{root_class}",
Header { nav_items: admin_nav_items, right_content: right_content }
main { class: "{main_class}",
Outlet::<Route> {}
}
Footer {}
}
}
}
(true, None) => {
rsx! {
div { class: "{root_class}",
div { class: "flex-1 flex items-center justify-center",
p { class: "text-gray-600 dark:text-[#9b9c9d]", "未登录,正在跳转..." }
}
}
}
}
(false, _) => {
// 使用与真实布局完全相同的结构包裹内容骨架,避免 checked 变化时的布局闪烁
rsx! {
div { class: "{root_class}",
Header { nav_items: admin_nav_items, right_content: right_content }
main { class: "{main_class}",
div { class: if show_skeleton() { "" } else { "opacity-0" },
{match route {
Route::Write {} => rsx! { WriteSkeleton {} },
_ => rsx! { AdminDashboardSkeleton {} },
}}
}
}
Footer {}
}
}
}
}
}