diff --git a/src/components/ui.rs b/src/components/ui.rs index 59927a7..d9652ca 100644 --- a/src/components/ui.rs +++ b/src/components/ui.rs @@ -122,6 +122,10 @@ pub const BTN_ICON: &str = /// signal 翻页(与路由式 `prev_route`/`next_route` 互斥,回调优先);不传则保持 /// 原路由式 ``。后台单路由 tab(如「管理文章」列表)用回调翻页, /// 前台/其他分页页面仍走路由,零影响。 +/// - `on_jump`:可选跳页回调。传入且总页数 > 1 时,admin 变体的当前页码渲染为 +/// 可编辑输入框——聚焦编辑、回车跳转(自动夹取到 `[1, total_pages]`,非法输入 +/// 还原为当前页)、失焦回显。仅回调式翻页可用——路由式翻页无法由任意页码反推 +/// `Route`,故路由式调用方不传此 prop,页码保持纯文本。 #[component] pub fn Pagination( variant: &'static str, @@ -133,6 +137,7 @@ pub fn Pagination( unit: &'static str, #[props(default)] on_prev: Option>, #[props(default)] on_next: Option>, + #[props(default)] on_jump: Option>, ) -> Element { let has_prev = current_page > 1; let total_pages = ((total + per_page as i64 - 1) / per_page as i64).max(1) as i32; @@ -165,6 +170,13 @@ pub fn Pagination( "下一页" span { class: "ml-1", "»" } }; + + // 跳页状态:当前页码本身渲染为可编辑输入框(仅回调式翻页且多页时)。 + // `jump_editing` = 输入框聚焦态——聚焦期间显示草稿,失焦回显 current_page, + // 与父组件翻页天然同步,无需 effect 桥接;`jump_draft` = 用户输入草稿。 + // 两者皆为独立 UI 状态(非派生镜像),合法 use_signal。 + let mut jump_editing: Signal = use_signal(|| false); + let mut jump_draft: Signal = use_signal(String::new); rsx! { nav { class: if is_admin { "flex mt-6 justify-between" } else { "flex mt-10 mb-6 justify-between" }, if has_prev { @@ -184,10 +196,42 @@ pub fn Pagination( } } - // admin 显示页码计数。 + // admin 显示页码计数;回调式翻页且多页时,当前页码可直接编辑跳页。 if is_admin { - span { class: "text-sm text-paper-secondary self-center", - "{current_page} / {total_pages} 页 (共 {total} {unit})" + span { class: "flex items-center gap-1.5 self-center text-sm text-paper-secondary", + if total_pages > 1 && on_jump.is_some() { + input { + class: "w-11 px-1 py-0.5 text-sm text-center bg-transparent text-paper-primary border border-paper-border rounded-full hover:border-paper-accent/60 focus:outline-none focus:border-paper-accent transition-colors", + r#type: "text", + inputmode: "numeric", + title: "输入页码,回车跳转", + value: if jump_editing() { jump_draft() } else { current_page.to_string() }, + onfocus: move |_| { + jump_draft.set(current_page.to_string()); + jump_editing.set(true); + }, + onblur: move |_| jump_editing.set(false), + oninput: move |e| jump_draft.set(e.value()), + onkeydown: move |e| { + if e.key() == Key::Enter { + if let Some(on_jump) = on_jump { + // 合法页码:夹取到 [1, total_pages] 后回调并回显; + // 非法输入(空串/非数字):还原为当前页。 + if let Ok(p) = jump_draft().trim().parse::() { + let p = p.clamp(1, total_pages); + on_jump.call(p); + jump_draft.set(p.to_string()); + } else { + jump_draft.set(current_page.to_string()); + } + } + } + }, + } + } else { + "{current_page}" + } + " / {total_pages} 页 (共 {total} {unit})" } } diff --git a/src/pages/admin/assets.rs b/src/pages/admin/assets.rs index 76d46bd..70b969e 100644 --- a/src/pages/admin/assets.rs +++ b/src/pages/admin/assets.rs @@ -523,6 +523,7 @@ pub fn Assets() -> Element { unit: "张", on_prev: move |_| page.set((page() - 1).max(1)), on_next: move |_| page.set(page() + 1), + on_jump: move |p: i32| page.set(p), } } } diff --git a/src/pages/admin/posts.rs b/src/pages/admin/posts.rs index 3ef097a..b0e4cb4 100644 --- a/src/pages/admin/posts.rs +++ b/src/pages/admin/posts.rs @@ -246,6 +246,12 @@ fn AllPostsList() -> Element { page.with_mut(|p| *p += 1); } }, + on_jump: { + let mut page = current_page; + move |p: i32| { + page.set(p); + } + }, } } } diff --git a/src/pages/admin/posts_trash.rs b/src/pages/admin/posts_trash.rs index 2d1ea7c..b3b2a8e 100644 --- a/src/pages/admin/posts_trash.rs +++ b/src/pages/admin/posts_trash.rs @@ -298,6 +298,12 @@ pub(super) fn PostsTrashPanel() -> Element { page.with_mut(|p| *p += 1); } }, + on_jump: { + let mut page = current_page; + move |p: i32| { + page.set(p); + } + }, } } }