From 1c56fd8ec19a5e7616678656e919a94332f9d742 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 9 Jul 2026 16:22:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(home,tags):=20=E5=90=8C=E5=8F=98=E4=BD=93?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=88=86=E9=A1=B5/=E5=88=87=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E5=90=8E=E5=88=97=E8=A1=A8=E4=B8=8D=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 延续上一个 commit(fix(post))发现的同类 bug,统一修复 home 和 tags: - home: /page/1 → /page/2(同 Route::HomePage 变体)后文章列表不更新 - tags: /tags/a → /tags/b(同 Route::TagDetail 变体)后标签下文章不更新 根因完全一致:use_server_future 闭包内读取的 current_page / tag 是普通 prop (i32 / String),move 进闭包后成为冻结快照,不建立反应式订阅,同变体导航 复用组件实例时 future 不重跑。 注意 / → /page/2(Route::Home → Route::HomePage,跨变体)不受影响,因为跨 变体会新挂载组件、future 首次运行;只有同变体间的 prop 变化才触发 bug。 修复:闭包内通过 router().current::() 读取当前 page/tag 建立订阅。 Home(/ 路由,Route::Home 变体无 page 字段)调用 HomePosts 时走兜底分支用 传入的 current_page。 验证(headless chromium,本地设 APP_BASE_URL 绕过 CSRF 403): - home 修复前:/page/1 点下一页,URL 变 /page/2,首篇仍是 page1 内容(复现) - home 修复后:首篇随分页变为 page2 内容 - tags 因数据库无多文章标签,无法端到端验证;代码模式与 home 一致且编译通过 --- src/pages/home.rs | 20 ++++++++++++++++++-- src/pages/tags.rs | 17 ++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/pages/home.rs b/src/pages/home.rs index 2a8803e..6956456 100644 --- a/src/pages/home.rs +++ b/src/pages/home.rs @@ -51,10 +51,26 @@ pub fn HomePage(page: i32) -> Element { /// /// 通过 `use_server_future` 异步获取当前页文章; /// 加载中显示骨架屏,加载失败显示错误提示,成功则渲染文章卡片与分页。 +/// +/// # 反应式取数 +/// 同 `post_detail.rs`:`current_page` 是普通 `i32` prop,被 `move` 进 +/// `use_server_future` 闭包后成为冻结快照,读取它不建立反应式订阅。因此 +/// `/page/1 → /page/2`(同为 `Route::HomePage` 变体,复用组件实例)这样的 +/// 同变体分页导航无法触发 future 重跑。修复方式:在闭包内通过 +/// `router().current::()` 读取当前 page,建立订阅,路由变化即重跑。 +/// `Home`(`/`)调用时路由变体无 page 字段,兜底用传入的 `current_page`。 #[component] fn HomePosts(current_page: i32) -> Element { - // 调用 server function 获取已发布文章分页数据。 - let posts_res = use_server_future(move || list_published_posts(current_page, POSTS_PER_PAGE))?; + let router = dioxus::router::router(); + + let posts_res = use_server_future(move || { + let page = match router.current::() { + Route::HomePage { page } => page.max(1), + // / 路由(Route::Home)及其它变体:用 prop 兜底。 + _ => current_page, + }; + list_published_posts(page, POSTS_PER_PAGE) + })?; // 将结果映射为更便于本地使用的 (posts, total) 形式。 let posts_data = posts_res.read().as_ref().map(|r| match r { diff --git a/src/pages/tags.rs b/src/pages/tags.rs index ed5c459..e05a29c 100644 --- a/src/pages/tags.rs +++ b/src/pages/tags.rs @@ -120,9 +120,24 @@ pub fn TagDetail(tag: String) -> Element { /// /// 通过 `use_server_future` 调用 `get_posts_by_tag` 获取指定标签下的文章; /// 成功时渲染文章总数与文章卡片。 +/// +/// # 反应式取数 +/// 同 `post_detail.rs` / `home.rs`:`tag` 是普通 `String` prop,被 `move` 进 +/// `use_server_future` 闭包后成为冻结快照,不建立反应式订阅。从标签云点击 +/// 切换标签(`/tags/a → /tags/b`,同为 `Route::TagDetail` 变体,复用组件实例) +/// 时 future 不会重跑。修复:在闭包内通过 `router().current::()` 读取 +/// 当前 tag 建立订阅,路由变化即重跑。 #[component] fn TagDetailContent(tag: String) -> Element { - let posts_res = use_server_future(move || get_posts_by_tag(tag.clone(), None, None))?; + let router = dioxus::router::router(); + + let posts_res = use_server_future(move || { + let current_tag = match router.current::() { + Route::TagDetail { tag } => tag, + _ => tag.clone(), + }; + get_posts_by_tag(current_tag, None, None) + })?; // 将结果映射为 (posts, total) 形式以便渲染。 let posts_data = posts_res.read().as_ref().map(|r| match r {